summaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorAnton Arapov <anton@redhat.com>2012-04-16 10:05:28 +0200
committerAnton Arapov <anton@redhat.com>2012-04-16 10:05:28 +0200
commitb4b6116a13633898cf868f2f103c96a90c4c20f8 (patch)
tree93d1b7e2cfcdf473d8d4ff3ad141fa864f8491f6 /drivers/tty
parentedd4be777c953e5faafc80d091d3084b4343f5d3 (diff)
downloadkernel-uprobes-b4b6116a13633898cf868f2f103c96a90c4c20f8.tar.gz
kernel-uprobes-b4b6116a13633898cf868f2f103c96a90c4c20f8.tar.xz
kernel-uprobes-b4b6116a13633898cf868f2f103c96a90c4c20f8.zip
fedora kernel: d9aad82f3319f3cfd1aebc01234254ef0c37ad84v3.3.2-1
Signed-off-by: Anton Arapov <anton@redhat.com>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/Kconfig390
-rw-r--r--drivers/tty/Makefile31
-rw-r--r--drivers/tty/amiserial.c2168
-rw-r--r--drivers/tty/bfin_jtag_comm.c368
-rw-r--r--drivers/tty/cyclades.c4186
-rw-r--r--drivers/tty/ehv_bytechan.c881
-rw-r--r--drivers/tty/hvc/Kconfig119
-rw-r--r--drivers/tty/hvc/Makefile15
-rw-r--r--drivers/tty/hvc/hvc_beat.c134
-rw-r--r--drivers/tty/hvc/hvc_bfin_jtag.c105
-rw-r--r--drivers/tty/hvc/hvc_console.c976
-rw-r--r--drivers/tty/hvc/hvc_console.h123
-rw-r--r--drivers/tty/hvc/hvc_dcc.c106
-rw-r--r--drivers/tty/hvc/hvc_irq.c49
-rw-r--r--drivers/tty/hvc/hvc_iseries.c599
-rw-r--r--drivers/tty/hvc/hvc_iucv.c1337
-rw-r--r--drivers/tty/hvc/hvc_opal.c425
-rw-r--r--drivers/tty/hvc/hvc_rtas.c133
-rw-r--r--drivers/tty/hvc/hvc_tile.c68
-rw-r--r--drivers/tty/hvc/hvc_udbg.c96
-rw-r--r--drivers/tty/hvc/hvc_vio.c486
-rw-r--r--drivers/tty/hvc/hvc_xen.c273
-rw-r--r--drivers/tty/hvc/hvcs.c1616
-rw-r--r--drivers/tty/hvc/hvsi.c1253
-rw-r--r--drivers/tty/hvc/hvsi_lib.c426
-rw-r--r--drivers/tty/ipwireless/Makefile8
-rw-r--r--drivers/tty/ipwireless/hardware.c1764
-rw-r--r--drivers/tty/ipwireless/hardware.h62
-rw-r--r--drivers/tty/ipwireless/main.c347
-rw-r--r--drivers/tty/ipwireless/main.h68
-rw-r--r--drivers/tty/ipwireless/network.c508
-rw-r--r--drivers/tty/ipwireless/network.h53
-rw-r--r--drivers/tty/ipwireless/setup_protocol.h108
-rw-r--r--drivers/tty/ipwireless/tty.c679
-rw-r--r--drivers/tty/ipwireless/tty.h45
-rw-r--r--drivers/tty/isicom.c1736
-rw-r--r--drivers/tty/moxa.c2085
-rw-r--r--drivers/tty/moxa.h304
-rw-r--r--drivers/tty/mxser.c2746
-rw-r--r--drivers/tty/mxser.h150
-rw-r--r--drivers/tty/n_gsm.c3165
-rw-r--r--drivers/tty/n_hdlc.c1006
-rw-r--r--drivers/tty/n_r3964.c1263
-rw-r--r--drivers/tty/n_tracerouter.c243
-rw-r--r--drivers/tty/n_tracesink.c238
-rw-r--r--drivers/tty/n_tracesink.h36
-rw-r--r--drivers/tty/n_tty.c2128
-rw-r--r--drivers/tty/nozomi.c1971
-rw-r--r--drivers/tty/pty.c788
-rw-r--r--drivers/tty/rocket.c3152
-rw-r--r--drivers/tty/rocket.h111
-rw-r--r--drivers/tty/rocket_int.h1214
-rw-r--r--drivers/tty/serial/21285.c511
-rw-r--r--drivers/tty/serial/68328serial.c1413
-rw-r--r--drivers/tty/serial/68328serial.h186
-rw-r--r--drivers/tty/serial/8250/8250.c3382
-rw-r--r--drivers/tty/serial/8250/8250.h105
-rw-r--r--drivers/tty/serial/8250/8250_accent.c45
-rw-r--r--drivers/tty/serial/8250/8250_acorn.c141
-rw-r--r--drivers/tty/serial/8250/8250_boca.c59
-rw-r--r--drivers/tty/serial/8250/8250_dw.c184
-rw-r--r--drivers/tty/serial/8250/8250_early.c287
-rw-r--r--drivers/tty/serial/8250/8250_exar_st16c554.c50
-rw-r--r--drivers/tty/serial/8250/8250_fourport.c51
-rw-r--r--drivers/tty/serial/8250/8250_fsl.c63
-rw-r--r--drivers/tty/serial/8250/8250_gsc.c122
-rw-r--r--drivers/tty/serial/8250/8250_hp300.c327
-rw-r--r--drivers/tty/serial/8250/8250_hub6.c56
-rw-r--r--drivers/tty/serial/8250/8250_mca.c61
-rw-r--r--drivers/tty/serial/8250/8250_pci.c4223
-rw-r--r--drivers/tty/serial/8250/8250_pnp.c524
-rw-r--r--drivers/tty/serial/8250/Kconfig280
-rw-r--r--drivers/tty/serial/8250/Makefile20
-rw-r--r--drivers/tty/serial/8250/serial_cs.c870
-rw-r--r--drivers/tty/serial/Kconfig1350
-rw-r--r--drivers/tty/serial/Makefile80
-rw-r--r--drivers/tty/serial/altera_jtaguart.c515
-rw-r--r--drivers/tty/serial/altera_uart.c659
-rw-r--r--drivers/tty/serial/amba-pl010.c837
-rw-r--r--drivers/tty/serial/amba-pl011.c2067
-rw-r--r--drivers/tty/serial/apbuart.c697
-rw-r--r--drivers/tty/serial/apbuart.h64
-rw-r--r--drivers/tty/serial/ar933x_uart.c688
-rw-r--r--drivers/tty/serial/atmel_serial.c1910
-rw-r--r--drivers/tty/serial/bcm63xx_uart.c901
-rw-r--r--drivers/tty/serial/bfin_sport_uart.c936
-rw-r--r--drivers/tty/serial/bfin_sport_uart.h87
-rw-r--r--drivers/tty/serial/bfin_uart.c1602
-rw-r--r--drivers/tty/serial/clps711x.c577
-rw-r--r--drivers/tty/serial/cpm_uart/Makefile11
-rw-r--r--drivers/tty/serial/cpm_uart/cpm_uart.h145
-rw-r--r--drivers/tty/serial/cpm_uart/cpm_uart_core.c1441
-rw-r--r--drivers/tty/serial/cpm_uart/cpm_uart_cpm1.c136
-rw-r--r--drivers/tty/serial/cpm_uart/cpm_uart_cpm1.h32
-rw-r--r--drivers/tty/serial/cpm_uart/cpm_uart_cpm2.c172
-rw-r--r--drivers/tty/serial/cpm_uart/cpm_uart_cpm2.h32
-rw-r--r--drivers/tty/serial/crisv10.c4566
-rw-r--r--drivers/tty/serial/crisv10.h147
-rw-r--r--drivers/tty/serial/dz.c956
-rw-r--r--drivers/tty/serial/dz.h129
-rw-r--r--drivers/tty/serial/icom.c1658
-rw-r--r--drivers/tty/serial/icom.h287
-rw-r--r--drivers/tty/serial/ifx6x60.c1414
-rw-r--r--drivers/tty/serial/ifx6x60.h129
-rw-r--r--drivers/tty/serial/imx.c1612
-rw-r--r--drivers/tty/serial/ioc3_serial.c2200
-rw-r--r--drivers/tty/serial/ioc4_serial.c2954
-rw-r--r--drivers/tty/serial/ip22zilog.c1221
-rw-r--r--drivers/tty/serial/ip22zilog.h281
-rw-r--r--drivers/tty/serial/jsm/Makefile8
-rw-r--r--drivers/tty/serial/jsm/jsm.h378
-rw-r--r--drivers/tty/serial/jsm/jsm_driver.c279
-rw-r--r--drivers/tty/serial/jsm/jsm_neo.c1413
-rw-r--r--drivers/tty/serial/jsm/jsm_tty.c842
-rw-r--r--drivers/tty/serial/kgdboc.c329
-rw-r--r--drivers/tty/serial/lantiq.c758
-rw-r--r--drivers/tty/serial/m32r_sio.c1191
-rw-r--r--drivers/tty/serial/m32r_sio.h48
-rw-r--r--drivers/tty/serial/m32r_sio_reg.h152
-rw-r--r--drivers/tty/serial/max3100.c928
-rw-r--r--drivers/tty/serial/max3107.c1215
-rw-r--r--drivers/tty/serial/max3107.h441
-rw-r--r--drivers/tty/serial/mcf.c662
-rw-r--r--drivers/tty/serial/mfd.c1511
-rw-r--r--drivers/tty/serial/mpc52xx_uart.c1525
-rw-r--r--drivers/tty/serial/mpsc.c2159
-rw-r--r--drivers/tty/serial/mrst_max3110.c912
-rw-r--r--drivers/tty/serial/mrst_max3110.h61
-rw-r--r--drivers/tty/serial/msm_serial.c974
-rw-r--r--drivers/tty/serial/msm_serial.h187
-rw-r--r--drivers/tty/serial/msm_serial_hs.c1879
-rw-r--r--drivers/tty/serial/msm_smd_tty.c235
-rw-r--r--drivers/tty/serial/mux.c635
-rw-r--r--drivers/tty/serial/mxs-auart.c799
-rw-r--r--drivers/tty/serial/netx-serial.c748
-rw-r--r--drivers/tty/serial/nwpserial.c479
-rw-r--r--drivers/tty/serial/of_serial.c207
-rw-r--r--drivers/tty/serial/omap-serial.c1687
-rw-r--r--drivers/tty/serial/pch_uart.c1787
-rw-r--r--drivers/tty/serial/pmac_zilog.c2059
-rw-r--r--drivers/tty/serial/pmac_zilog.h383
-rw-r--r--drivers/tty/serial/pnx8xxx_uart.c854
-rw-r--r--drivers/tty/serial/pxa.c897
-rw-r--r--drivers/tty/serial/sa1100.c916
-rw-r--r--drivers/tty/serial/samsung.c1699
-rw-r--r--drivers/tty/serial/samsung.h97
-rw-r--r--drivers/tty/serial/sb1250-duart.c975
-rw-r--r--drivers/tty/serial/sc26xx.c745
-rw-r--r--drivers/tty/serial/serial_core.c2550
-rw-r--r--drivers/tty/serial/serial_ks8695.c704
-rw-r--r--drivers/tty/serial/serial_txx9.c1344
-rw-r--r--drivers/tty/serial/sh-sci.c2469
-rw-r--r--drivers/tty/serial/sh-sci.h40
-rw-r--r--drivers/tty/serial/sirfsoc_uart.c783
-rw-r--r--drivers/tty/serial/sirfsoc_uart.h185
-rw-r--r--drivers/tty/serial/sn_console.c1086
-rw-r--r--drivers/tty/serial/suncore.c247
-rw-r--r--drivers/tty/serial/suncore.h33
-rw-r--r--drivers/tty/serial/sunhv.c661
-rw-r--r--drivers/tty/serial/sunsab.c1152
-rw-r--r--drivers/tty/serial/sunsab.h322
-rw-r--r--drivers/tty/serial/sunsu.c1608
-rw-r--r--drivers/tty/serial/sunzilog.c1655
-rw-r--r--drivers/tty/serial/sunzilog.h289
-rw-r--r--drivers/tty/serial/timbuart.c521
-rw-r--r--drivers/tty/serial/timbuart.h58
-rw-r--r--drivers/tty/serial/uartlite.c653
-rw-r--r--drivers/tty/serial/ucc_uart.c1544
-rw-r--r--drivers/tty/serial/vr41xx_siu.c967
-rw-r--r--drivers/tty/serial/vt8500_serial.c645
-rw-r--r--drivers/tty/serial/xilinx_uartps.c1116
-rw-r--r--drivers/tty/serial/zs.c1305
-rw-r--r--drivers/tty/serial/zs.h284
-rw-r--r--drivers/tty/synclink.c8116
-rw-r--r--drivers/tty/synclink_gt.c5161
-rw-r--r--drivers/tty/synclinkmp.c5600
-rw-r--r--drivers/tty/sysrq.c905
-rw-r--r--drivers/tty/tty_audit.c360
-rw-r--r--drivers/tty/tty_buffer.c522
-rw-r--r--drivers/tty/tty_io.c3410
-rw-r--r--drivers/tty/tty_ioctl.c1198
-rw-r--r--drivers/tty/tty_ldisc.c973
-rw-r--r--drivers/tty/tty_mutex.c32
-rw-r--r--drivers/tty/tty_port.c448
-rw-r--r--drivers/tty/vt/Makefile34
-rw-r--r--drivers/tty/vt/consolemap.c780
-rw-r--r--drivers/tty/vt/cp437.uni291
-rw-r--r--drivers/tty/vt/defkeymap.c_shipped262
-rw-r--r--drivers/tty/vt/defkeymap.map357
-rw-r--r--drivers/tty/vt/keyboard.c1454
-rw-r--r--drivers/tty/vt/selection.c345
-rw-r--r--drivers/tty/vt/vc_screen.c666
-rw-r--r--drivers/tty/vt/vt.c4226
-rw-r--r--drivers/tty/vt/vt_ioctl.c1799
194 files changed, 180008 insertions, 0 deletions
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
new file mode 100644
index 00000000000..830cd62d849
--- /dev/null
+++ b/drivers/tty/Kconfig
@@ -0,0 +1,390 @@
+config VT
+ bool "Virtual terminal" if EXPERT
+ depends on !S390 && !UML
+ select INPUT
+ default y
+ ---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 CONSOLE_TRANSLATIONS
+ depends on VT
+ default y
+ bool "Enable character translations in console" if EXPERT
+ ---help---
+ This enables support for font mapping and Unicode translation
+ on virtual consoles.
+
+config VT_CONSOLE
+ bool "Support for console on virtual terminal" if EXPERT
+ 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 VT_CONSOLE_SLEEP
+ def_bool y
+ depends on VT_CONSOLE && PM_SLEEP
+
+config HW_CONSOLE
+ bool
+ depends on VT && !UML
+ default y
+
+config VT_HW_CONSOLE_BINDING
+ bool "Support for binding and unbinding console drivers"
+ depends on HW_CONSOLE
+ default n
+ ---help---
+ The virtual terminal is the device that interacts with the physical
+ terminal through console drivers. On these systems, at least one
+ console driver is loaded. In other configurations, additional console
+ drivers may be enabled, such as the framebuffer console. If more than
+ 1 console driver is enabled, setting this to 'y' will allow you to
+ select the console driver that will serve as the backend for the
+ virtual terminals.
+
+ See <file:Documentation/console/console.txt> for more
+ information. For framebuffer console users, please refer to
+ <file:Documentation/fb/fbcon.txt>.
+
+config UNIX98_PTYS
+ bool "Unix98 PTY support" if EXPERT
+ 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 DEVPTS_MULTIPLE_INSTANCES
+ bool "Support multiple instances of devpts"
+ depends on UNIX98_PTYS
+ default n
+ ---help---
+ Enable support for multiple instances of devpts filesystem.
+ If you want to have isolated PTY namespaces (eg: in containers),
+ say Y here. Otherwise, say N. If enabled, each mount of devpts
+ filesystem with the '-o newinstance' option will create an
+ independent PTY namespace.
+
+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 0 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 BFIN_JTAG_COMM
+ tristate "Blackfin JTAG Communication"
+ depends on BLACKFIN
+ help
+ Add support for emulating a TTY device over the Blackfin JTAG.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bfin_jtag_comm.
+
+config BFIN_JTAG_COMM_CONSOLE
+ bool "Console on Blackfin JTAG"
+ depends on BFIN_JTAG_COMM=y
+
+config SERIAL_NONSTANDARD
+ bool "Non-standard serial port support"
+ depends on HAS_IOMEM
+ ---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 ROCKETPORT
+ tristate "Comtrol RocketPort support"
+ depends on SERIAL_NONSTANDARD && (ISA || EISA || PCI)
+ 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/serial/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 && (PCI || ISA)
+ select FW_LOADER
+ ---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:Documentation/serial/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 MOXA_INTELLIO
+ tristate "Moxa Intellio support"
+ depends on SERIAL_NONSTANDARD && (ISA || EISA || PCI)
+ select FW_LOADER
+ 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 v. 2.0"
+ depends on SERIAL_NONSTANDARD && (PCI || EISA || ISA)
+ help
+ Say Y here if you have a Moxa SmartIO multiport serial card and/or
+ want to help develop a new version of this driver.
+
+ This is upgraded (1.9.1) driver from original Moxa drivers with
+ changes finally resulting in PCI probing.
+
+ This driver can also be built as a module. The module will be called
+ mxser. If you want to do that, say M here.
+
+config SYNCLINK
+ tristate "Microgate SyncLink card support"
+ depends on SERIAL_NONSTANDARD && PCI && ISA_DMA_API
+ 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 && PCI
+ 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 SYNCLINK_GT
+ tristate "SyncLink GT/AC support"
+ depends on SERIAL_NONSTANDARD && PCI
+ help
+ Support for SyncLink GT and SyncLink AC families of
+ synchronous and asynchronous serial adapters
+ manufactured by Microgate Systems, Ltd. (www.microgate.com)
+
+config NOZOMI
+ tristate "HSDPA Broadband Wireless Data Card - Globe Trotter"
+ depends on PCI && EXPERIMENTAL
+ help
+ If you have a HSDPA driver Broadband Wireless Data Card -
+ Globe Trotter PCMCIA card, say Y here.
+
+ To compile this driver as a module, choose M here, the module
+ will be called nozomi.
+
+config ISI
+ tristate "Multi-Tech multiport card support (EXPERIMENTAL)"
+ depends on SERIAL_NONSTANDARD && PCI
+ select FW_LOADER
+ 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 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 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 N_GSM
+ tristate "GSM MUX line discipline support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ depends on NET
+ help
+ This line discipline provides support for the GSM MUX protocol and
+ presents the mux as a set of 61 individual tty devices.
+
+config TRACE_ROUTER
+ tristate "Trace data router for MIPI P1149.7 cJTAG standard"
+ depends on TRACE_SINK
+ default n
+ help
+ The trace router uses the Linux tty line discipline framework to
+ route trace data coming from a tty port (say UART for example) to
+ the trace sink line discipline driver and to another tty port (say
+ USB). This is part of a solution for the MIPI P1149.7, compact JTAG,
+ standard, which is for debugging mobile devices. The PTI driver in
+ drivers/misc/pti.c defines the majority of this MIPI solution.
+
+ You should select this driver if the target kernel is meant for
+ a mobile device containing a modem. Then you will need to select
+ "Trace data sink for MIPI P1149.7 cJTAG standard" line discipline
+ driver.
+
+config TRACE_SINK
+ tristate "Trace data sink for MIPI P1149.7 cJTAG standard"
+ default n
+ help
+ The trace sink uses the Linux line discipline framework to receive
+ trace data coming from the trace router line discipline driver
+ to a user-defined tty port target, like USB.
+ This is to provide a way to extract modem trace data on
+ devices that do not have a PTI HW module, or just need modem
+ trace data to come out of a different HW output port.
+ This is part of a solution for the P1149.7, compact JTAG, standard.
+
+ If you select this option, you need to select
+ "Trace data router for MIPI P1149.7 cJTAG standard".
+
+config PPC_EPAPR_HV_BYTECHAN
+ tristate "ePAPR hypervisor byte channel driver"
+ depends on PPC
+ help
+ This driver creates /dev entries for each ePAPR hypervisor byte
+ channel, thereby allowing applications to communicate with byte
+ channels as if they were serial ports.
+
+config PPC_EARLY_DEBUG_EHV_BC
+ bool "Early console (udbg) support for ePAPR hypervisors"
+ depends on PPC_EPAPR_HV_BYTECHAN=y
+ help
+ Select this option to enable early console (a.k.a. "udbg") support
+ via an ePAPR byte channel. You also need to choose the byte channel
+ handle below.
+
+config PPC_EARLY_DEBUG_EHV_BC_HANDLE
+ int "Byte channel handle for early console (udbg)"
+ depends on PPC_EARLY_DEBUG_EHV_BC
+ default 0
+ help
+ If you want early console (udbg) output through a byte channel,
+ specify the handle of the byte channel to use.
+
+ For this to work, the byte channel driver must be compiled
+ in-kernel, not as a module.
+
+ Note that only one early console driver can be enabled, so don't
+ enable any others if you enable this one.
+
+ If the number you specify is not a valid byte channel handle, then
+ there simply will be no early console output. This is true also
+ if you don't boot under a hypervisor at all.
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
new file mode 100644
index 00000000000..2953059530e
--- /dev/null
+++ b/drivers/tty/Makefile
@@ -0,0 +1,31 @@
+obj-y += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \
+ tty_buffer.o tty_port.o tty_mutex.o
+obj-$(CONFIG_LEGACY_PTYS) += pty.o
+obj-$(CONFIG_UNIX98_PTYS) += pty.o
+obj-$(CONFIG_AUDIT) += tty_audit.o
+obj-$(CONFIG_MAGIC_SYSRQ) += sysrq.o
+obj-$(CONFIG_N_HDLC) += n_hdlc.o
+obj-$(CONFIG_N_GSM) += n_gsm.o
+obj-$(CONFIG_TRACE_ROUTER) += n_tracerouter.o
+obj-$(CONFIG_TRACE_SINK) += n_tracesink.o
+obj-$(CONFIG_R3964) += n_r3964.o
+
+obj-y += vt/
+obj-$(CONFIG_HVC_DRIVER) += hvc/
+obj-y += serial/
+
+# tty drivers
+obj-$(CONFIG_AMIGA_BUILTIN_SERIAL) += amiserial.o
+obj-$(CONFIG_BFIN_JTAG_COMM) += bfin_jtag_comm.o
+obj-$(CONFIG_CYCLADES) += cyclades.o
+obj-$(CONFIG_ISI) += isicom.o
+obj-$(CONFIG_MOXA_INTELLIO) += moxa.o
+obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
+obj-$(CONFIG_NOZOMI) += nozomi.o
+obj-$(CONFIG_ROCKETPORT) += rocket.o
+obj-$(CONFIG_SYNCLINK_GT) += synclink_gt.o
+obj-$(CONFIG_SYNCLINKMP) += synclinkmp.o
+obj-$(CONFIG_SYNCLINK) += synclink.o
+obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o
+
+obj-y += ipwireless/
diff --git a/drivers/tty/amiserial.c b/drivers/tty/amiserial.c
new file mode 100644
index 00000000000..b84c83456dc
--- /dev/null
+++ b/drivers/tty/amiserial.c
@@ -0,0 +1,2168 @@
+/*
+ * 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/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 */
+
+#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/seq_file.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+
+#include <asm/setup.h>
+
+#include <asm/system.h>
+
+#include <asm/irq.h>
+
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+
+#define custom amiga_custom
+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 ktermios *old);
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout);
+
+
+static struct serial_state rs_table[1];
+
+#define NR_PORTS ARRAY_SIZE(rs_table)
+
+#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 = 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 = 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 void rs_sched_event(struct async_struct *info,
+ int event)
+{
+ info->event |= 1 << event;
+ tasklet_schedule(&info->tlet);
+}
+
+static void receive_chars(struct async_struct *info)
+{
+ int status;
+ int serdatr;
+ struct tty_struct *tty = info->tty;
+ unsigned char ch, flag;
+ struct async_icount *icount;
+ int oe = 0;
+
+ 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;
+ icount->rx++;
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("DR%02x:%02x...", ch, status);
+#endif
+ flag = TTY_NORMAL;
+
+ /*
+ * 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 out;
+
+ status &= info->read_status_mask;
+
+ if (status & (UART_LSR_BI)) {
+#ifdef SERIAL_DEBUG_INTR
+ printk("handling break....");
+#endif
+ flag = TTY_BREAK;
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ } else if (status & UART_LSR_PE)
+ flag = TTY_PARITY;
+ else if (status & UART_LSR_FE)
+ flag = TTY_FRAME;
+ if (status & UART_LSR_OE) {
+ /*
+ * Overrun is special, since it's
+ * reported immediately, and doesn't
+ * affect the current character
+ */
+ oe = 1;
+ }
+ }
+ tty_insert_flip_char(tty, ch, flag);
+ if (oe == 1)
+ tty_insert_flip_char(tty, 0, TTY_OVERRUN);
+ tty_flip_buffer_push(tty);
+out:
+ return;
+}
+
+static 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 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)
+{
+ /* 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 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 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);
+}
+
+/*
+ * ---------------------------------------------------------------
+ * 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 ktermios *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) {
+ /* FIXME: Will need updating for new tty in the end */
+ 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 disable the VBL interrupt ?
+ */
+
+ /*
+ * Set up parity check flag
+ */
+
+ 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 int rs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct async_struct *info;
+ unsigned long flags;
+
+ info = tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "rs_put_char"))
+ return 0;
+
+ if (!info->xmit.buf)
+ return 0;
+
+ local_irq_save(flags);
+ if (CIRC_SPACE(info->xmit.head,
+ info->xmit.tail,
+ SERIAL_XMIT_SIZE) == 0) {
+ local_irq_restore(flags);
+ return 0;
+ }
+
+ info->xmit.buf[info->xmit.head++] = ch;
+ info->xmit.head &= SERIAL_XMIT_SIZE-1;
+ local_irq_restore(flags);
+ return 1;
+}
+
+static void rs_flush_chars(struct tty_struct *tty)
+{
+ struct async_struct *info = 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;
+ unsigned long flags;
+
+ info = tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "rs_write"))
+ return 0;
+
+ if (!info->xmit.buf)
+ return 0;
+
+ local_irq_save(flags);
+ 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 = 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 = 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 = 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);
+ 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 = 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 = 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 = 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 __user * retinfo)
+{
+ struct serial_struct tmp;
+ struct serial_state *state = info->state;
+
+ if (!retinfo)
+ return -EFAULT;
+ memset(&tmp, 0, sizeof(tmp));
+ tty_lock();
+ 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;
+ tty_unlock();
+ if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
+ return -EFAULT;
+ return 0;
+}
+
+static int set_serial_info(struct async_struct * info,
+ struct serial_struct __user * 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;
+
+ tty_lock();
+ 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)) {
+ tty_unlock();
+ 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) {
+ tty_unlock();
+ 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);
+ tty_unlock();
+ 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 __user *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 async_struct * info = 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, unsigned int set,
+ unsigned int clear)
+{
+ struct async_struct * info = 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 int rs_break(struct tty_struct *tty, int break_state)
+{
+ struct async_struct * info = tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_break"))
+ return -EINVAL;
+
+ local_irq_save(flags);
+ if (break_state == -1)
+ custom.adkcon = AC_SETCLR | AC_UARTBRK;
+ else
+ custom.adkcon = AC_UARTBRK;
+ mb();
+ local_irq_restore(flags);
+ return 0;
+}
+
+/*
+ * 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.
+ */
+static int rs_get_icount(struct tty_struct *tty,
+ struct serial_icounter_struct *icount)
+{
+ struct async_struct *info = tty->driver_data;
+ struct async_icount cnow;
+ unsigned long flags;
+
+ 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;
+
+ return 0;
+}
+
+static int rs_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct async_struct * info = tty->driver_data;
+ struct async_icount cprev, cnow; /* kernel counter temps */
+ 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 != TIOCSERGSTRUCT) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ 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:
+ return 0;
+
+ case TIOCSERGETLSR: /* Get line status register */
+ return get_lsr_info(info, argp);
+
+ case TIOCSERGSTRUCT:
+ if (copy_to_user(argp,
+ 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 */
+
+ 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 ktermios *old_termios)
+{
+ struct async_struct *info = tty->driver_data;
+ unsigned long flags;
+ unsigned int cflag = tty->termios->c_cflag;
+
+ 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 = 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);
+ rs_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 = 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;
+ }
+ __set_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 = 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
+ tty_unlock();
+ schedule();
+ tty_lock();
+ }
+ __set_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 = kzalloc(sizeof(struct async_struct), GFP_KERNEL);
+ if (!info) {
+ sstate->count--;
+ return -ENOMEM;
+ }
+#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;
+
+ 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 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 void line_info(struct seq_file *m, struct serial_state *state)
+{
+ struct async_struct *info = state->info, scr_info;
+ char stat_buf[30], control, status;
+ unsigned long flags;
+
+ seq_printf(m, "%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) {
+ seq_printf(m, " baud:%d", state->baud_base / info->quot);
+ }
+
+ seq_printf(m, " tx:%d rx:%d", state->icount.tx, state->icount.rx);
+
+ if (state->icount.frame)
+ seq_printf(m, " fe:%d", state->icount.frame);
+
+ if (state->icount.parity)
+ seq_printf(m, " pe:%d", state->icount.parity);
+
+ if (state->icount.brk)
+ seq_printf(m, " brk:%d", state->icount.brk);
+
+ if (state->icount.overrun)
+ seq_printf(m, " oe:%d", state->icount.overrun);
+
+ /*
+ * Last thing is the RS-232 status lines
+ */
+ seq_printf(m, " %s\n", stat_buf+1);
+}
+
+static int rs_proc_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "serinfo:1.0 driver:%s\n", serial_version);
+ line_info(m, &rs_table[0]);
+ return 0;
+}
+
+static int rs_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rs_proc_show, NULL);
+}
+
+static const struct file_operations rs_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = rs_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * ---------------------------------------------------------------------
+ * 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 void show_serial_version(void)
+{
+ printk(KERN_INFO "%s version %s\n", serial_name, serial_version);
+}
+
+
+static const 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,
+ .tiocmget = rs_tiocmget,
+ .tiocmset = rs_tiocmset,
+ .get_icount = rs_get_icount,
+ .proc_fops = &rs_proc_fops,
+};
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+static int __init amiga_serial_probe(struct platform_device *pdev)
+{
+ unsigned long flags;
+ struct serial_state * state;
+ int error;
+
+ serial_driver = alloc_tty_driver(1);
+ if (!serial_driver)
+ return -ENOMEM;
+
+ 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);
+
+ error = tty_register_driver(serial_driver);
+ if (error)
+ goto fail_put_tty_driver;
+
+ 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;
+
+ 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;
+
+ /* set ISRs, and then disable the rx interrupts */
+ error = request_irq(IRQ_AMIGA_TBE, ser_tx_int, 0, "serial TX", state);
+ if (error)
+ goto fail_unregister;
+
+ error = request_irq(IRQ_AMIGA_RBF, ser_rx_int, 0,
+ "serial RX", state);
+ if (error)
+ goto fail_free_irq;
+
+ local_irq_save(flags);
+
+ /* 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 */
+
+ platform_set_drvdata(pdev, state);
+
+ return 0;
+
+fail_free_irq:
+ free_irq(IRQ_AMIGA_TBE, state);
+fail_unregister:
+ tty_unregister_driver(serial_driver);
+fail_put_tty_driver:
+ put_tty_driver(serial_driver);
+ return error;
+}
+
+static int __exit amiga_serial_remove(struct platform_device *pdev)
+{
+ int error;
+ struct serial_state *state = platform_get_drvdata(pdev);
+ struct async_struct *info = state->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);
+
+ rs_table[0].info = NULL;
+ kfree(info);
+
+ free_irq(IRQ_AMIGA_TBE, rs_table);
+ free_irq(IRQ_AMIGA_RBF, rs_table);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return error;
+}
+
+static struct platform_driver amiga_serial_driver = {
+ .remove = __exit_p(amiga_serial_remove),
+ .driver = {
+ .name = "amiga-serial",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init amiga_serial_init(void)
+{
+ return platform_driver_probe(&amiga_serial_driver, amiga_serial_probe);
+}
+
+module_init(amiga_serial_init);
+
+static void __exit amiga_serial_exit(void)
+{
+ platform_driver_unregister(&amiga_serial_driver);
+}
+
+module_exit(amiga_serial_exit);
+
+
+#if defined(CONFIG_SERIAL_CONSOLE) && !defined(MODULE)
+
+/*
+ * ------------------------------------------------------------
+ * Serial console driver
+ * ------------------------------------------------------------
+ */
+
+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 /* CONFIG_SERIAL_CONSOLE && !MODULE */
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:amiga-serial");
diff --git a/drivers/tty/bfin_jtag_comm.c b/drivers/tty/bfin_jtag_comm.c
new file mode 100644
index 00000000000..3a997760ec3
--- /dev/null
+++ b/drivers/tty/bfin_jtag_comm.c
@@ -0,0 +1,368 @@
+/*
+ * TTY over Blackfin JTAG Communication
+ *
+ * Copyright 2008-2009 Analog Devices Inc.
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#define DRV_NAME "bfin-jtag-comm"
+#define DEV_NAME "ttyBFJC"
+#define pr_fmt(fmt) DRV_NAME ": " fmt
+
+#include <linux/circ_buf.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/atomic.h>
+
+#define pr_init(fmt, args...) ({ static const __initconst char __fmt[] = fmt; printk(__fmt, ## args); })
+
+/* See the Debug/Emulation chapter in the HRM */
+#define EMUDOF 0x00000001 /* EMUDAT_OUT full & valid */
+#define EMUDIF 0x00000002 /* EMUDAT_IN full & valid */
+#define EMUDOOVF 0x00000004 /* EMUDAT_OUT overflow */
+#define EMUDIOVF 0x00000008 /* EMUDAT_IN overflow */
+
+static inline uint32_t bfin_write_emudat(uint32_t emudat)
+{
+ __asm__ __volatile__("emudat = %0;" : : "d"(emudat));
+ return emudat;
+}
+
+static inline uint32_t bfin_read_emudat(void)
+{
+ uint32_t emudat;
+ __asm__ __volatile__("%0 = emudat;" : "=d"(emudat));
+ return emudat;
+}
+
+static inline uint32_t bfin_write_emudat_chars(char a, char b, char c, char d)
+{
+ return bfin_write_emudat((a << 0) | (b << 8) | (c << 16) | (d << 24));
+}
+
+#define CIRC_SIZE 2048 /* see comment in tty_io.c:do_tty_write() */
+#define CIRC_MASK (CIRC_SIZE - 1)
+#define circ_empty(circ) ((circ)->head == (circ)->tail)
+#define circ_free(circ) CIRC_SPACE((circ)->head, (circ)->tail, CIRC_SIZE)
+#define circ_cnt(circ) CIRC_CNT((circ)->head, (circ)->tail, CIRC_SIZE)
+#define circ_byte(circ, idx) ((circ)->buf[(idx) & CIRC_MASK])
+
+static struct tty_driver *bfin_jc_driver;
+static struct task_struct *bfin_jc_kthread;
+static struct tty_struct * volatile bfin_jc_tty;
+static unsigned long bfin_jc_count;
+static DEFINE_MUTEX(bfin_jc_tty_mutex);
+static volatile struct circ_buf bfin_jc_write_buf;
+
+static int
+bfin_jc_emudat_manager(void *arg)
+{
+ uint32_t inbound_len = 0, outbound_len = 0;
+
+ while (!kthread_should_stop()) {
+ /* no one left to give data to, so sleep */
+ if (bfin_jc_tty == NULL && circ_empty(&bfin_jc_write_buf)) {
+ pr_debug("waiting for readers\n");
+ __set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ __set_current_state(TASK_RUNNING);
+ }
+
+ /* no data available, so just chill */
+ if (!(bfin_read_DBGSTAT() & EMUDIF) && circ_empty(&bfin_jc_write_buf)) {
+ pr_debug("waiting for data (in_len = %i) (circ: %i %i)\n",
+ inbound_len, bfin_jc_write_buf.tail, bfin_jc_write_buf.head);
+ if (inbound_len)
+ schedule();
+ else
+ schedule_timeout_interruptible(HZ);
+ continue;
+ }
+
+ /* if incoming data is ready, eat it */
+ if (bfin_read_DBGSTAT() & EMUDIF) {
+ struct tty_struct *tty;
+ mutex_lock(&bfin_jc_tty_mutex);
+ tty = (struct tty_struct *)bfin_jc_tty;
+ if (tty != NULL) {
+ uint32_t emudat = bfin_read_emudat();
+ if (inbound_len == 0) {
+ pr_debug("incoming length: 0x%08x\n", emudat);
+ inbound_len = emudat;
+ } else {
+ size_t num_chars = (4 <= inbound_len ? 4 : inbound_len);
+ pr_debug(" incoming data: 0x%08x (pushing %zu)\n", emudat, num_chars);
+ inbound_len -= num_chars;
+ tty_insert_flip_string(tty, (unsigned char *)&emudat, num_chars);
+ tty_flip_buffer_push(tty);
+ }
+ }
+ mutex_unlock(&bfin_jc_tty_mutex);
+ }
+
+ /* if outgoing data is ready, post it */
+ if (!(bfin_read_DBGSTAT() & EMUDOF) && !circ_empty(&bfin_jc_write_buf)) {
+ if (outbound_len == 0) {
+ outbound_len = circ_cnt(&bfin_jc_write_buf);
+ bfin_write_emudat(outbound_len);
+ pr_debug("outgoing length: 0x%08x\n", outbound_len);
+ } else {
+ struct tty_struct *tty;
+ int tail = bfin_jc_write_buf.tail;
+ size_t ate = (4 <= outbound_len ? 4 : outbound_len);
+ uint32_t emudat =
+ bfin_write_emudat_chars(
+ circ_byte(&bfin_jc_write_buf, tail + 0),
+ circ_byte(&bfin_jc_write_buf, tail + 1),
+ circ_byte(&bfin_jc_write_buf, tail + 2),
+ circ_byte(&bfin_jc_write_buf, tail + 3)
+ );
+ bfin_jc_write_buf.tail += ate;
+ outbound_len -= ate;
+ mutex_lock(&bfin_jc_tty_mutex);
+ tty = (struct tty_struct *)bfin_jc_tty;
+ if (tty)
+ tty_wakeup(tty);
+ mutex_unlock(&bfin_jc_tty_mutex);
+ pr_debug(" outgoing data: 0x%08x (pushing %zu)\n", emudat, ate);
+ }
+ }
+ }
+
+ __set_current_state(TASK_RUNNING);
+ return 0;
+}
+
+static int
+bfin_jc_open(struct tty_struct *tty, struct file *filp)
+{
+ mutex_lock(&bfin_jc_tty_mutex);
+ pr_debug("open %lu\n", bfin_jc_count);
+ ++bfin_jc_count;
+ bfin_jc_tty = tty;
+ wake_up_process(bfin_jc_kthread);
+ mutex_unlock(&bfin_jc_tty_mutex);
+ return 0;
+}
+
+static void
+bfin_jc_close(struct tty_struct *tty, struct file *filp)
+{
+ mutex_lock(&bfin_jc_tty_mutex);
+ pr_debug("close %lu\n", bfin_jc_count);
+ if (--bfin_jc_count == 0)
+ bfin_jc_tty = NULL;
+ wake_up_process(bfin_jc_kthread);
+ mutex_unlock(&bfin_jc_tty_mutex);
+}
+
+/* XXX: we dont handle the put_char() case where we must handle count = 1 */
+static int
+bfin_jc_circ_write(const unsigned char *buf, int count)
+{
+ int i;
+ count = min(count, circ_free(&bfin_jc_write_buf));
+ pr_debug("going to write chunk of %i bytes\n", count);
+ for (i = 0; i < count; ++i)
+ circ_byte(&bfin_jc_write_buf, bfin_jc_write_buf.head + i) = buf[i];
+ bfin_jc_write_buf.head += i;
+ return i;
+}
+
+#ifndef CONFIG_BFIN_JTAG_COMM_CONSOLE
+# define console_lock()
+# define console_unlock()
+#endif
+static int
+bfin_jc_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ int i;
+ console_lock();
+ i = bfin_jc_circ_write(buf, count);
+ console_unlock();
+ wake_up_process(bfin_jc_kthread);
+ return i;
+}
+
+static void
+bfin_jc_flush_chars(struct tty_struct *tty)
+{
+ wake_up_process(bfin_jc_kthread);
+}
+
+static int
+bfin_jc_write_room(struct tty_struct *tty)
+{
+ return circ_free(&bfin_jc_write_buf);
+}
+
+static int
+bfin_jc_chars_in_buffer(struct tty_struct *tty)
+{
+ return circ_cnt(&bfin_jc_write_buf);
+}
+
+static void
+bfin_jc_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ unsigned long expire = jiffies + timeout;
+ while (!circ_empty(&bfin_jc_write_buf)) {
+ if (signal_pending(current))
+ break;
+ if (time_after(jiffies, expire))
+ break;
+ }
+}
+
+static const struct tty_operations bfin_jc_ops = {
+ .open = bfin_jc_open,
+ .close = bfin_jc_close,
+ .write = bfin_jc_write,
+ /*.put_char = bfin_jc_put_char,*/
+ .flush_chars = bfin_jc_flush_chars,
+ .write_room = bfin_jc_write_room,
+ .chars_in_buffer = bfin_jc_chars_in_buffer,
+ .wait_until_sent = bfin_jc_wait_until_sent,
+};
+
+static int __init bfin_jc_init(void)
+{
+ int ret;
+
+ bfin_jc_kthread = kthread_create(bfin_jc_emudat_manager, NULL, DRV_NAME);
+ if (IS_ERR(bfin_jc_kthread))
+ return PTR_ERR(bfin_jc_kthread);
+
+ ret = -ENOMEM;
+
+ bfin_jc_write_buf.head = bfin_jc_write_buf.tail = 0;
+ bfin_jc_write_buf.buf = kmalloc(CIRC_SIZE, GFP_KERNEL);
+ if (!bfin_jc_write_buf.buf)
+ goto err_buf;
+
+ bfin_jc_driver = alloc_tty_driver(1);
+ if (!bfin_jc_driver)
+ goto err_driver;
+
+ bfin_jc_driver->owner = THIS_MODULE;
+ bfin_jc_driver->driver_name = DRV_NAME;
+ bfin_jc_driver->name = DEV_NAME;
+ bfin_jc_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ bfin_jc_driver->subtype = SERIAL_TYPE_NORMAL;
+ bfin_jc_driver->init_termios = tty_std_termios;
+ tty_set_operations(bfin_jc_driver, &bfin_jc_ops);
+
+ ret = tty_register_driver(bfin_jc_driver);
+ if (ret)
+ goto err;
+
+ pr_init(KERN_INFO DRV_NAME ": initialized\n");
+
+ return 0;
+
+ err:
+ put_tty_driver(bfin_jc_driver);
+ err_driver:
+ kfree(bfin_jc_write_buf.buf);
+ err_buf:
+ kthread_stop(bfin_jc_kthread);
+ return ret;
+}
+module_init(bfin_jc_init);
+
+static void __exit bfin_jc_exit(void)
+{
+ kthread_stop(bfin_jc_kthread);
+ kfree(bfin_jc_write_buf.buf);
+ tty_unregister_driver(bfin_jc_driver);
+ put_tty_driver(bfin_jc_driver);
+}
+module_exit(bfin_jc_exit);
+
+#if defined(CONFIG_BFIN_JTAG_COMM_CONSOLE) || defined(CONFIG_EARLY_PRINTK)
+static void
+bfin_jc_straight_buffer_write(const char *buf, unsigned count)
+{
+ unsigned ate = 0;
+ while (bfin_read_DBGSTAT() & EMUDOF)
+ continue;
+ bfin_write_emudat(count);
+ while (ate < count) {
+ while (bfin_read_DBGSTAT() & EMUDOF)
+ continue;
+ bfin_write_emudat_chars(buf[ate], buf[ate+1], buf[ate+2], buf[ate+3]);
+ ate += 4;
+ }
+}
+#endif
+
+#ifdef CONFIG_BFIN_JTAG_COMM_CONSOLE
+static void
+bfin_jc_console_write(struct console *co, const char *buf, unsigned count)
+{
+ if (bfin_jc_kthread == NULL)
+ bfin_jc_straight_buffer_write(buf, count);
+ else
+ bfin_jc_circ_write(buf, count);
+}
+
+static struct tty_driver *
+bfin_jc_console_device(struct console *co, int *index)
+{
+ *index = co->index;
+ return bfin_jc_driver;
+}
+
+static struct console bfin_jc_console = {
+ .name = DEV_NAME,
+ .write = bfin_jc_console_write,
+ .device = bfin_jc_console_device,
+ .flags = CON_ANYTIME | CON_PRINTBUFFER,
+ .index = -1,
+};
+
+static int __init bfin_jc_console_init(void)
+{
+ register_console(&bfin_jc_console);
+ return 0;
+}
+console_initcall(bfin_jc_console_init);
+#endif
+
+#ifdef CONFIG_EARLY_PRINTK
+static void __init
+bfin_jc_early_write(struct console *co, const char *buf, unsigned int count)
+{
+ bfin_jc_straight_buffer_write(buf, count);
+}
+
+static struct __initdata console bfin_jc_early_console = {
+ .name = "early_BFJC",
+ .write = bfin_jc_early_write,
+ .flags = CON_ANYTIME | CON_PRINTBUFFER,
+ .index = -1,
+};
+
+struct console * __init
+bfin_jc_early_init(unsigned int port, unsigned int cflag)
+{
+ return &bfin_jc_early_console;
+}
+#endif
+
+MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
+MODULE_DESCRIPTION("TTY over Blackfin JTAG Communication");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/cyclades.c b/drivers/tty/cyclades.c
new file mode 100644
index 00000000000..c9bf779481d
--- /dev/null
+++ b/drivers/tty/cyclades.c
@@ -0,0 +1,4186 @@
+#undef BLOCKMOVE
+#define Z_WAKE
+#undef Z_EXT_CHARS_IN_BUFFER
+
+/*
+ * 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>.
+ *
+ * Copyright (C) 2007-2009 Jiri Slaby <jirislaby@gmail.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.
+ * Converted to pci probing and cleaned up by Jiri Slaby.
+ *
+ */
+
+#define CY_VERSION "2.6"
+
+/* 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 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_INTERRUPTS
+#undef CY_16Y_HACK
+#undef CY_ENABLE_MONITORING
+#undef CY_PCI_DEBUG
+
+/*
+ * Include section
+ */
+#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/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/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 <linux/firmware.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+static void cy_send_xchar(struct tty_struct *tty, char ch);
+
+#ifndef SERIAL_XMIT_SIZE
+#define SERIAL_XMIT_SIZE (min(PAGE_SIZE, 4096))
+#endif
+
+#define STD_COM_FLAGS (0)
+
+/* firmware stuff */
+#define ZL_MAX_BLOCKS 16
+#define DRIVER_VERSION 0x02010203
+#define RAM_SIZE 0x80000
+
+enum zblock_type {
+ ZBLOCK_PRG = 0,
+ ZBLOCK_FPGA = 1
+};
+
+struct zfile_header {
+ char name[64];
+ char date[32];
+ char aux[32];
+ u32 n_config;
+ u32 config_offset;
+ u32 n_blocks;
+ u32 block_offset;
+ u32 reserved[9];
+} __attribute__ ((packed));
+
+struct zfile_config {
+ char name[64];
+ u32 mailbox;
+ u32 function;
+ u32 n_blocks;
+ u32 block_list[ZL_MAX_BLOCKS];
+} __attribute__ ((packed));
+
+struct zfile_block {
+ u32 type;
+ u32 file_offset;
+ u32 ram_offset;
+ u32 size;
+} __attribute__ ((packed));
+
+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.
+*/
+
+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 ARRAY_SIZE(cy_isa_addresses)
+
+static long maddr[NR_CARDS];
+static int irq[NR_CARDS];
+
+module_param_array(maddr, long, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+
+#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];
+
+static int cy_next_channel; /* next minor available */
+
+/*
+ * 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->port.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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 const unsigned int cy_chip_offset[] = { 0x0000,
+ 0x0400,
+ 0x0800,
+ 0x0C00,
+ 0x0200,
+ 0x0600,
+ 0x0A00,
+ 0x0E00
+};
+
+/* PCI related definitions */
+
+#ifdef CONFIG_PCI
+static const struct pci_device_id cy_pci_dev_id[] = {
+ /* PCI < 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_Y_Lo) },
+ /* PCI > 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_Y_Hi) },
+ /* 4Y PCI < 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_4Y_Lo) },
+ /* 4Y PCI > 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_4Y_Hi) },
+ /* 8Y PCI < 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_8Y_Lo) },
+ /* 8Y PCI > 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_8Y_Hi) },
+ /* Z PCI < 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_Z_Lo) },
+ /* Z PCI > 1Mb */
+ { PCI_DEVICE(PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_CYCLOM_Z_Hi) },
+ { } /* end of table */
+};
+MODULE_DEVICE_TABLE(pci, cy_pci_dev_id);
+#endif
+
+static void cy_start(struct tty_struct *);
+static void cy_set_line_char(struct cyclades_port *, struct tty_struct *);
+static int cyz_issue_cmd(struct cyclades_card *, __u32, __u8, __u32);
+#ifdef CONFIG_ISA
+static unsigned detect_isa_irq(void __iomem *);
+#endif /* CONFIG_ISA */
+
+#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 DEFINE_TIMER(cyz_timerlist, 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 void cyy_writeb(struct cyclades_port *port, u32 reg, u8 val)
+{
+ struct cyclades_card *card = port->card;
+
+ cy_writeb(port->u.cyy.base_addr + (reg << card->bus_index), val);
+}
+
+static inline u8 cyy_readb(struct cyclades_port *port, u32 reg)
+{
+ struct cyclades_card *card = port->card;
+
+ return readb(port->u.cyy.base_addr + (reg << card->bus_index));
+}
+
+static inline bool cy_is_Z(struct cyclades_card *card)
+{
+ return card->num_chips == (unsigned int)-1;
+}
+
+static inline bool __cyz_fpga_loaded(struct RUNTIME_9060 __iomem *ctl_addr)
+{
+ return readl(&ctl_addr->init_ctrl) & (1 << 17);
+}
+
+static inline bool cyz_fpga_loaded(struct cyclades_card *card)
+{
+ return __cyz_fpga_loaded(card->ctl_addr.p9060);
+}
+
+static inline bool cyz_is_loaded(struct cyclades_card *card)
+{
+ struct FIRM_ID __iomem *fw_id = card->base_addr + ID_ADDRESS;
+
+ return (card->hw_ver == ZO_V1 || cyz_fpga_loaded(card)) &&
+ readl(&fw_id->signature) == ZFIRM_ID;
+}
+
+static inline int serial_paranoia_check(struct cyclades_port *info,
+ const char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+ if (!info) {
+ printk(KERN_WARNING "cyc Warning: null cyclades_port for (%s) "
+ "in %s\n", name, routine);
+ return 1;
+ }
+
+ if (info->magic != CYCLADES_MAGIC) {
+ printk(KERN_WARNING "cyc Warning: bad magic number for serial "
+ "struct (%s) in %s\n", name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/***********************************************************/
+/********* 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, u8 cmd, int index)
+{
+ void __iomem *ccr = base_addr + (CyCCR << index);
+ unsigned int i;
+
+ /* Check to see that the previous command has completed */
+ for (i = 0; i < 100; i++) {
+ if (readb(ccr) == 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(ccr, cmd);
+
+ return 0;
+}
+
+static inline int cyy_issue_cmd(struct cyclades_port *port, u8 cmd)
+{
+ return __cyy_issue_cmd(port->u.cyy.base_addr, cmd,
+ port->card->bus_index);
+}
+
+#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 ... */
+ msleep(5);
+
+ /* 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),
+ readb(address + (CySRER << index)) | CyTxRdy);
+ local_irq_restore(flags);
+
+ /* Wait ... */
+ msleep(5);
+
+ /* Check which interrupt is in use */
+ irq = probe_irq_off(irqs);
+
+ /* Clean up */
+ save_xir = (u_char) readb(address + (CyTIR << index));
+ save_car = readb(address + (CyCAR << index));
+ cy_writeb(address + (CyCAR << index), (save_xir & 0x3));
+ cy_writeb(address + (CySRER << index),
+ 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 */
+
+static void cyy_chip_rx(struct cyclades_card *cinfo, int chip,
+ void __iomem *base_addr)
+{
+ struct cyclades_port *info;
+ struct tty_struct *tty;
+ int len, index = cinfo->bus_index;
+ u8 ivr, save_xir, channel, save_car, data, char_count;
+
+#ifdef CY_DEBUG_INTERRUPTS
+ printk(KERN_DEBUG "cyy_interrupt: rcvd intr, chip %d\n", chip);
+#endif
+ /* determine the channel & change to that context */
+ save_xir = readb(base_addr + (CyRIR << index));
+ channel = save_xir & CyIRChannel;
+ info = &cinfo->ports[channel + chip * 4];
+ save_car = cyy_readb(info, CyCAR);
+ cyy_writeb(info, CyCAR, save_xir);
+ ivr = cyy_readb(info, CyRIVR) & CyIVRMask;
+
+ tty = tty_port_tty_get(&info->port);
+ /* if there is nowhere to put the data, discard it */
+ if (tty == NULL) {
+ if (ivr == CyIVRRxEx) { /* exception */
+ data = cyy_readb(info, CyRDSR);
+ } else { /* normal character reception */
+ char_count = cyy_readb(info, CyRDCR);
+ while (char_count--)
+ data = cyy_readb(info, CyRDSR);
+ }
+ goto end;
+ }
+ /* there is an open port for this data */
+ if (ivr == CyIVRRxEx) { /* exception */
+ data = cyy_readb(info, CyRDSR);
+
+ /* 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++;
+ tty_kref_put(tty);
+ return;
+ }
+ if (tty_buffer_request_room(tty, 1)) {
+ if (data & info->read_status_mask) {
+ if (data & CyBREAK) {
+ tty_insert_flip_char(tty,
+ cyy_readb(info, CyRDSR),
+ TTY_BREAK);
+ info->icount.rx++;
+ if (info->port.flags & ASYNC_SAK)
+ do_SAK(tty);
+ } else if (data & CyFRAME) {
+ tty_insert_flip_char(tty,
+ cyy_readb(info, CyRDSR),
+ TTY_FRAME);
+ info->icount.rx++;
+ info->idle_stats.frame_errs++;
+ } else if (data & CyPARITY) {
+ /* Pieces of seven... */
+ tty_insert_flip_char(tty,
+ cyy_readb(info, CyRDSR),
+ TTY_PARITY);
+ info->icount.rx++;
+ info->idle_stats.parity_errs++;
+ } else if (data & CyOVERRUN) {
+ tty_insert_flip_char(tty, 0,
+ TTY_OVERRUN);
+ info->icount.rx++;
+ /* If the flip buffer itself is
+ overflowing, we still lose
+ the next incoming character.
+ */
+ tty_insert_flip_char(tty,
+ cyy_readb(info, CyRDSR),
+ TTY_FRAME);
+ 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_insert_flip_char(tty, 0,
+ TTY_NORMAL);
+ info->icount.rx++;
+ }
+ } else {
+ tty_insert_flip_char(tty, 0, TTY_NORMAL);
+ 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 = cyy_readb(info, CyRDCR);
+
+#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
+ len = tty_buffer_request_room(tty, char_count);
+ while (len--) {
+ data = cyy_readb(info, CyRDSR);
+ tty_insert_flip_char(tty, data, TTY_NORMAL);
+ info->idle_stats.recv_bytes++;
+ info->icount.rx++;
+#ifdef CY_16Y_HACK
+ udelay(10L);
+#endif
+ }
+ info->idle_stats.recv_idle = jiffies;
+ }
+ tty_schedule_flip(tty);
+ tty_kref_put(tty);
+end:
+ /* end of service */
+ cyy_writeb(info, CyRIR, save_xir & 0x3f);
+ cyy_writeb(info, CyCAR, save_car);
+}
+
+static void cyy_chip_tx(struct cyclades_card *cinfo, unsigned int chip,
+ void __iomem *base_addr)
+{
+ struct cyclades_port *info;
+ struct tty_struct *tty;
+ int char_count, index = cinfo->bus_index;
+ u8 save_xir, channel, save_car, outch;
+
+ /* 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(KERN_DEBUG "cyy_interrupt: xmit intr, chip %d\n", chip);
+#endif
+
+ /* determine the channel & change to that context */
+ save_xir = readb(base_addr + (CyTIR << index));
+ channel = save_xir & CyIRChannel;
+ save_car = readb(base_addr + (CyCAR << index));
+ cy_writeb(base_addr + (CyCAR << index), save_xir);
+
+ info = &cinfo->ports[channel + chip * 4];
+ tty = tty_port_tty_get(&info->port);
+ if (tty == NULL) {
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) & ~CyTxRdy);
+ goto end;
+ }
+
+ /* 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;
+ cyy_writeb(info, CyTDR, outch);
+ char_count--;
+ info->icount.tx++;
+ info->x_char = 0;
+ }
+
+ if (info->breakon || info->breakoff) {
+ if (info->breakon) {
+ cyy_writeb(info, CyTDR, 0);
+ cyy_writeb(info, CyTDR, 0x81);
+ info->breakon = 0;
+ char_count -= 2;
+ }
+ if (info->breakoff) {
+ cyy_writeb(info, CyTDR, 0);
+ cyy_writeb(info, CyTDR, 0x83);
+ info->breakoff = 0;
+ char_count -= 2;
+ }
+ }
+
+ while (char_count-- > 0) {
+ if (!info->xmit_cnt) {
+ if (cyy_readb(info, CySRER) & CyTxMpty) {
+ cyy_writeb(info, CySRER,
+ cyy_readb(info, CySRER) & ~CyTxMpty);
+ } else {
+ cyy_writeb(info, CySRER, CyTxMpty |
+ (cyy_readb(info, CySRER) & ~CyTxRdy));
+ }
+ goto done;
+ }
+ if (info->port.xmit_buf == NULL) {
+ cyy_writeb(info, CySRER,
+ cyy_readb(info, CySRER) & ~CyTxRdy);
+ goto done;
+ }
+ if (tty->stopped || tty->hw_stopped) {
+ cyy_writeb(info, CySRER,
+ cyy_readb(info, CySRER) & ~CyTxRdy);
+ goto done;
+ }
+ /* 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->port.xmit_buf[info->xmit_tail];
+ if (outch) {
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1) &
+ (SERIAL_XMIT_SIZE - 1);
+ cyy_writeb(info, CyTDR, outch);
+ info->icount.tx++;
+ } else {
+ if (char_count > 1) {
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1) &
+ (SERIAL_XMIT_SIZE - 1);
+ cyy_writeb(info, CyTDR, outch);
+ cyy_writeb(info, CyTDR, 0);
+ info->icount.tx++;
+ char_count--;
+ }
+ }
+ }
+
+done:
+ tty_wakeup(tty);
+ tty_kref_put(tty);
+end:
+ /* end of service */
+ cyy_writeb(info, CyTIR, save_xir & 0x3f);
+ cyy_writeb(info, CyCAR, save_car);
+}
+
+static void cyy_chip_modem(struct cyclades_card *cinfo, int chip,
+ void __iomem *base_addr)
+{
+ struct cyclades_port *info;
+ struct tty_struct *tty;
+ int index = cinfo->bus_index;
+ u8 save_xir, channel, save_car, mdm_change, mdm_status;
+
+ /* determine the channel & change to that context */
+ save_xir = readb(base_addr + (CyMIR << index));
+ channel = save_xir & CyIRChannel;
+ info = &cinfo->ports[channel + chip * 4];
+ save_car = cyy_readb(info, CyCAR);
+ cyy_writeb(info, CyCAR, save_xir);
+
+ mdm_change = cyy_readb(info, CyMISR);
+ mdm_status = cyy_readb(info, CyMSVR1);
+
+ tty = tty_port_tty_get(&info->port);
+ if (!tty)
+ goto end;
+
+ 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++;
+
+ wake_up_interruptible(&info->port.delta_msr_wait);
+ }
+
+ if ((mdm_change & CyDCD) && (info->port.flags & ASYNC_CHECK_CD)) {
+ if (mdm_status & CyDCD)
+ wake_up_interruptible(&info->port.open_wait);
+ else
+ tty_hangup(tty);
+ }
+ if ((mdm_change & CyCTS) && (info->port.flags & ASYNC_CTS_FLOW)) {
+ if (tty->hw_stopped) {
+ if (mdm_status & CyCTS) {
+ /* cy_start isn't used
+ because... !!! */
+ tty->hw_stopped = 0;
+ cyy_writeb(info, CySRER,
+ cyy_readb(info, CySRER) | CyTxRdy);
+ tty_wakeup(tty);
+ }
+ } else {
+ if (!(mdm_status & CyCTS)) {
+ /* cy_stop isn't used
+ because ... !!! */
+ tty->hw_stopped = 1;
+ cyy_writeb(info, CySRER,
+ cyy_readb(info, CySRER) & ~CyTxRdy);
+ }
+ }
+ }
+/* if (mdm_change & CyDSR) {
+ }
+ if (mdm_change & CyRI) {
+ }*/
+ tty_kref_put(tty);
+end:
+ /* end of service */
+ cyy_writeb(info, CyMIR, save_xir & 0x3f);
+ cyy_writeb(info, CyCAR, save_car);
+}
+
+/* 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)
+{
+ int status;
+ struct cyclades_card *cinfo = dev_id;
+ void __iomem *base_addr, *card_base_addr;
+ unsigned int chip, too_many, had_work;
+ int index;
+
+ if (unlikely(cinfo == NULL)) {
+#ifdef CY_DEBUG_INTERRUPTS
+ printk(KERN_DEBUG "cyy_interrupt: spurious interrupt %d\n",
+ irq);
+#endif
+ return IRQ_NONE; /* spurious interrupt */
+ }
+
+ card_base_addr = cinfo->base_addr;
+ index = cinfo->bus_index;
+
+ /* card was not initialized yet (e.g. DEBUG_SHIRQ) */
+ if (unlikely(card_base_addr == NULL))
+ return IRQ_HANDLED;
+
+ /* 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 = 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;
+ spin_lock(&cinfo->card_lock);
+ if (status & CySRReceive) /* rx intr */
+ cyy_chip_rx(cinfo, chip, base_addr);
+ if (status & CySRTransmit) /* tx intr */
+ cyy_chip_tx(cinfo, chip, base_addr);
+ if (status & CySRModem) /* modem intr */
+ cyy_chip_modem(cinfo, chip, base_addr);
+ spin_unlock(&cinfo->card_lock);
+ }
+ }
+ } 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 */
+
+static void cyy_change_rts_dtr(struct cyclades_port *info, unsigned int set,
+ unsigned int clear)
+{
+ struct cyclades_card *card = info->card;
+ int channel = info->line - card->first_line;
+ u32 rts, dtr, msvrr, msvrd;
+
+ channel &= 0x03;
+
+ if (info->rtsdtr_inv) {
+ msvrr = CyMSVR2;
+ msvrd = CyMSVR1;
+ rts = CyDTR;
+ dtr = CyRTS;
+ } else {
+ msvrr = CyMSVR1;
+ msvrd = CyMSVR2;
+ rts = CyRTS;
+ dtr = CyDTR;
+ }
+ if (set & TIOCM_RTS) {
+ cyy_writeb(info, CyCAR, channel);
+ cyy_writeb(info, msvrr, rts);
+ }
+ if (clear & TIOCM_RTS) {
+ cyy_writeb(info, CyCAR, channel);
+ cyy_writeb(info, msvrr, ~rts);
+ }
+ if (set & TIOCM_DTR) {
+ cyy_writeb(info, CyCAR, channel);
+ cyy_writeb(info, msvrd, dtr);
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "cyc:set_modem_info raising DTR\n");
+ printk(KERN_DEBUG " status: 0x%x, 0x%x\n",
+ cyy_readb(info, CyMSVR1),
+ cyy_readb(info, CyMSVR2));
+#endif
+ }
+ if (clear & TIOCM_DTR) {
+ cyy_writeb(info, CyCAR, channel);
+ cyy_writeb(info, msvrd, ~dtr);
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "cyc:set_modem_info dropping DTR\n");
+ printk(KERN_DEBUG " status: 0x%x, 0x%x\n",
+ cyy_readb(info, CyMSVR1),
+ cyy_readb(info, CyMSVR2));
+#endif
+ }
+}
+
+/***********************************************************/
+/********* 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,
+ __u32 *channel, __u8 *cmd, __u32 *param)
+{
+ struct BOARD_CTRL __iomem *board_ctrl = cinfo->board_ctrl;
+ unsigned long loc_doorbell;
+
+ loc_doorbell = readl(&cinfo->ctl_addr.p9060->loc_doorbell);
+ if (loc_doorbell) {
+ *cmd = (char)(0xff & loc_doorbell);
+ *channel = readl(&board_ctrl->fwcmd_channel);
+ *param = (__u32) readl(&board_ctrl->fwcmd_param);
+ cy_writel(&cinfo->ctl_addr.p9060->loc_doorbell, 0xffffffff);
+ return 1;
+ }
+ return 0;
+} /* cyz_fetch_msg */
+
+static int
+cyz_issue_cmd(struct cyclades_card *cinfo,
+ __u32 channel, __u8 cmd, __u32 param)
+{
+ struct BOARD_CTRL __iomem *board_ctrl = cinfo->board_ctrl;
+ __u32 __iomem *pci_doorbell;
+ unsigned int index;
+
+ if (!cyz_is_loaded(cinfo))
+ return -1;
+
+ index = 0;
+ pci_doorbell = &cinfo->ctl_addr.p9060->pci_doorbell;
+ while ((readl(pci_doorbell) & 0xff) != 0) {
+ if (index++ == 1000)
+ return (int)(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, struct tty_struct *tty)
+{
+ struct BUF_CTRL __iomem *buf_ctrl = info->u.cyz.buf_ctrl;
+ struct cyclades_card *cinfo = info->card;
+ unsigned int char_count;
+ int len;
+#ifdef BLOCKMOVE
+ unsigned char *buf;
+#else
+ char data;
+#endif
+ __u32 rx_put, rx_get, new_rx_get, rx_bufsize, rx_bufaddr;
+
+ rx_get = new_rx_get = readl(&buf_ctrl->rx_get);
+ rx_put = readl(&buf_ctrl->rx_put);
+ rx_bufsize = readl(&buf_ctrl->rx_bufsize);
+ rx_bufaddr = 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) {
+#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 == NULL) {
+ /* 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 (1) {
+ len = tty_prepare_flip_string(tty, &buf,
+ char_count);
+ if (!len)
+ break;
+
+ len = min_t(unsigned int, min(len, char_count),
+ rx_bufsize - new_rx_get);
+
+ memcpy_fromio(buf, cinfo->base_addr +
+ rx_bufaddr + new_rx_get, len);
+
+ new_rx_get = (new_rx_get + len) &
+ (rx_bufsize - 1);
+ char_count -= len;
+ info->icount.rx += len;
+ info->idle_stats.recv_bytes += len;
+ }
+#else
+ len = tty_buffer_request_room(tty, char_count);
+ while (len--) {
+ data = readb(cinfo->base_addr + rx_bufaddr +
+ new_rx_get);
+ new_rx_get = (new_rx_get + 1) &
+ (rx_bufsize - 1);
+ tty_insert_flip_char(tty, data, TTY_NORMAL);
+ 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 = 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 >= readl(&buf_ctrl->rx_threshold) &&
+ !timer_pending(&cyz_rx_full_timer[
+ info->line]))
+ mod_timer(&cyz_rx_full_timer[info->line],
+ jiffies + 1);
+#endif
+ info->idle_stats.recv_idle = jiffies;
+ tty_schedule_flip(tty);
+ }
+ /* Update rx_get */
+ cy_writel(&buf_ctrl->rx_get, new_rx_get);
+ }
+}
+
+static void cyz_handle_tx(struct cyclades_port *info, struct tty_struct *tty)
+{
+ struct BUF_CTRL __iomem *buf_ctrl = info->u.cyz.buf_ctrl;
+ struct cyclades_card *cinfo = info->card;
+ u8 data;
+ unsigned int char_count;
+#ifdef BLOCKMOVE
+ int small_count;
+#endif
+ __u32 tx_put, tx_get, tx_bufsize, tx_bufaddr;
+
+ if (info->xmit_cnt <= 0) /* Nothing to transmit */
+ return;
+
+ tx_get = readl(&buf_ctrl->tx_get);
+ tx_put = readl(&buf_ctrl->tx_put);
+ tx_bufsize = readl(&buf_ctrl->tx_bufsize);
+ tx_bufaddr = 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 == NULL)
+ 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++;
+ }
+#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->port.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);
+ }
+#else
+ while (info->xmit_cnt && char_count) {
+ data = info->port.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++;
+ }
+#endif
+ tty_wakeup(tty);
+ztxdone:
+ /* Update tx_put */
+ cy_writel(&buf_ctrl->tx_put, tx_put);
+ }
+}
+
+static void cyz_handle_cmd(struct cyclades_card *cinfo)
+{
+ struct BOARD_CTRL __iomem *board_ctrl = cinfo->board_ctrl;
+ struct tty_struct *tty;
+ struct cyclades_port *info;
+ __u32 channel, param, fw_ver;
+ __u8 cmd;
+ int special_count;
+ int delta_count;
+
+ fw_ver = readl(&board_ctrl->fw_version);
+
+ while (cyz_fetch_msg(cinfo, &channel, &cmd, &param) == 1) {
+ special_count = 0;
+ delta_count = 0;
+ info = &cinfo->ports[channel];
+ tty = tty_port_tty_get(&info->port);
+ if (tty == NULL)
+ continue;
+
+ switch (cmd) {
+ case C_CM_PR_ERROR:
+ tty_insert_flip_char(tty, 0, TTY_PARITY);
+ info->icount.rx++;
+ special_count++;
+ break;
+ case C_CM_FR_ERROR:
+ tty_insert_flip_char(tty, 0, TTY_FRAME);
+ info->icount.rx++;
+ special_count++;
+ break;
+ case C_CM_RXBRK:
+ tty_insert_flip_char(tty, 0, TTY_BREAK);
+ info->icount.rx++;
+ special_count++;
+ break;
+ case C_CM_MDCD:
+ info->icount.dcd++;
+ delta_count++;
+ if (info->port.flags & ASYNC_CHECK_CD) {
+ u32 dcd = fw_ver > 241 ? param :
+ readl(&info->u.cyz.ch_ctrl->rs_status);
+ if (dcd & C_RS_DCD)
+ wake_up_interruptible(&info->port.open_wait);
+ else
+ tty_hangup(tty);
+ }
+ 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:
+ complete(&info->shutdown_wait);
+ 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(KERN_DEBUG "cyz_interrupt: rcvd intr, card %d, "
+ "port %ld\n", info->card, channel);
+#endif
+ cyz_handle_rx(info, tty);
+ break;
+ case C_CM_TXBEMPTY:
+ case C_CM_TXLOWWM:
+ case C_CM_INTBACK:
+ /* Transmission Interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+ printk(KERN_DEBUG "cyz_interrupt: xmit intr, card %d, "
+ "port %ld\n", info->card, channel);
+#endif
+ cyz_handle_tx(info, tty);
+ break;
+#endif /* CONFIG_CYZ_INTR */
+ case C_CM_FATAL:
+ /* should do something with this !!! */
+ break;
+ default:
+ break;
+ }
+ if (delta_count)
+ wake_up_interruptible(&info->port.delta_msr_wait);
+ if (special_count)
+ tty_schedule_flip(tty);
+ tty_kref_put(tty);
+ }
+}
+
+#ifdef CONFIG_CYZ_INTR
+static irqreturn_t cyz_interrupt(int irq, void *dev_id)
+{
+ struct cyclades_card *cinfo = dev_id;
+
+ if (unlikely(!cyz_is_loaded(cinfo))) {
+#ifdef CY_DEBUG_INTERRUPTS
+ printk(KERN_DEBUG "cyz_interrupt: board not yet loaded "
+ "(IRQ%d).\n", 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;
+ struct cyclades_card *card = info->card;
+ int retval;
+ __u32 channel = info->line - card->first_line;
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ retval = cyz_issue_cmd(card, channel, C_CM_INTBACK2, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:cyz_rx_restart retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+#else /* CONFIG_CYZ_INTR */
+
+static void cyz_poll(unsigned long arg)
+{
+ struct cyclades_card *cinfo;
+ struct cyclades_port *info;
+ unsigned long expires = jiffies + HZ;
+ unsigned int port, card;
+
+ for (card = 0; card < NR_CARDS; card++) {
+ cinfo = &cy_card[card];
+
+ if (!cy_is_Z(cinfo))
+ continue;
+ if (!cyz_is_loaded(cinfo))
+ continue;
+
+ /* Skip first polling cycle to avoid racing conditions with the FW */
+ if (!cinfo->intr_enabled) {
+ cinfo->intr_enabled = 1;
+ continue;
+ }
+
+ cyz_handle_cmd(cinfo);
+
+ for (port = 0; port < cinfo->nports; port++) {
+ struct tty_struct *tty;
+
+ info = &cinfo->ports[port];
+ tty = tty_port_tty_get(&info->port);
+ /* OK to pass NULL to the handle functions below.
+ They need to drop the data in that case. */
+
+ if (!info->throttle)
+ cyz_handle_rx(info, tty);
+ cyz_handle_tx(info, tty);
+ tty_kref_put(tty);
+ }
+ /* poll every 'cyz_polling_cycle' period */
+ expires = jiffies + cyz_polling_cycle;
+ }
+ mod_timer(&cyz_timerlist, expires);
+} /* 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 cy_startup(struct cyclades_port *info, struct tty_struct *tty)
+{
+ struct cyclades_card *card;
+ unsigned long flags;
+ int retval = 0;
+ int channel;
+ unsigned long page;
+
+ card = info->card;
+ channel = info->line - card->first_line;
+
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ if (info->port.flags & ASYNC_INITIALIZED)
+ goto errout;
+
+ if (!info->type) {
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ goto errout;
+ }
+
+ if (info->port.xmit_buf)
+ free_page(page);
+ else
+ info->port.xmit_buf = (unsigned char *)page;
+
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ cy_set_line_char(info, tty);
+
+ if (!cy_is_Z(card)) {
+ channel &= 0x03;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ cyy_writeb(info, CyCAR, channel);
+
+ cyy_writeb(info, CyRTPR,
+ (info->default_timeout ? info->default_timeout : 0x02));
+ /* 10ms rx timeout */
+
+ cyy_issue_cmd(info, CyCHAN_CTL | CyENB_RCVR | CyENB_XMTR);
+
+ cyy_change_rts_dtr(info, TIOCM_RTS | TIOCM_DTR, 0);
+
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) | CyRxData);
+ } else {
+ struct CH_CTRL __iomem *ch_ctrl = info->u.cyz.ch_ctrl;
+
+ if (!cyz_is_loaded(card))
+ return -ENODEV;
+
+#ifdef CY_DEBUG_OPEN
+ printk(KERN_DEBUG "cyc startup Z card %d, channel %d, "
+ "base_addr %p\n", card, channel, card->base_addr);
+#endif
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ cy_writel(&ch_ctrl->op_mode, C_CH_ENABLE);
+#ifdef Z_WAKE
+#ifdef CONFIG_CYZ_INTR
+ cy_writel(&ch_ctrl->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->intr_enable,
+ C_IN_IOCTLW | C_IN_MDCD);
+#endif /* CONFIG_CYZ_INTR */
+#else
+#ifdef CONFIG_CYZ_INTR
+ cy_writel(&ch_ctrl->intr_enable,
+ C_IN_TXBEMPTY | C_IN_TXLOWWM | C_IN_RXHIWM |
+ C_IN_RXNNDT | C_IN_MDCD);
+#else
+ cy_writel(&ch_ctrl->intr_enable, C_IN_MDCD);
+#endif /* CONFIG_CYZ_INTR */
+#endif /* Z_WAKE */
+
+ retval = cyz_issue_cmd(card, channel, C_CM_IOCTL, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "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(card, channel, C_CM_FLUSH_RX, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:startup(2) retval on ttyC%d was "
+ "%x\n", info->line, retval);
+ }
+
+ /* set timeout !!! */
+ /* set RTS and DTR !!! */
+ tty_port_raise_dtr_rts(&info->port);
+
+ /* enable send, recv, modem !!! */
+ }
+
+ info->port.flags |= ASYNC_INITIALIZED;
+
+ clear_bit(TTY_IO_ERROR, &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;
+
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+#ifdef CY_DEBUG_OPEN
+ printk(KERN_DEBUG "cyc startup done\n");
+#endif
+ return 0;
+
+errout:
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ free_page(page);
+ return retval;
+} /* startup */
+
+static void start_xmit(struct cyclades_port *info)
+{
+ struct cyclades_card *card = info->card;
+ unsigned long flags;
+ int channel = info->line - card->first_line;
+
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_writeb(info, CyCAR, channel & 0x03);
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) | CyTxRdy);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ } else {
+#ifdef CONFIG_CYZ_INTR
+ int retval;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ retval = cyz_issue_cmd(card, channel, C_CM_INTBACK, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:start_xmit retval on ttyC%d was "
+ "%x\n", info->line, retval);
+ }
+ spin_unlock_irqrestore(&card->card_lock, 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 cy_shutdown(struct cyclades_port *info, struct tty_struct *tty)
+{
+ struct cyclades_card *card;
+ unsigned long flags;
+
+ if (!(info->port.flags & ASYNC_INITIALIZED))
+ return;
+
+ card = info->card;
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ /* Clear delta_msr_wait queue to avoid mem leaks. */
+ wake_up_interruptible(&info->port.delta_msr_wait);
+
+ if (info->port.xmit_buf) {
+ unsigned char *temp;
+ temp = info->port.xmit_buf;
+ info->port.xmit_buf = NULL;
+ free_page((unsigned long)temp);
+ }
+ if (tty->termios->c_cflag & HUPCL)
+ cyy_change_rts_dtr(info, 0, TIOCM_RTS | TIOCM_DTR);
+
+ cyy_issue_cmd(info, CyCHAN_CTL | CyDIS_RCVR);
+ /* it may be appropriate to clear _XMIT at
+ some later date (after testing)!!! */
+
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ info->port.flags &= ~ASYNC_INITIALIZED;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ } else {
+#ifdef CY_DEBUG_OPEN
+ int channel = info->line - card->first_line;
+ printk(KERN_DEBUG "cyc shutdown Z card %d, channel %d, "
+ "base_addr %p\n", card, channel, card->base_addr);
+#endif
+
+ if (!cyz_is_loaded(card))
+ return;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ if (info->port.xmit_buf) {
+ unsigned char *temp;
+ temp = info->port.xmit_buf;
+ info->port.xmit_buf = NULL;
+ free_page((unsigned long)temp);
+ }
+
+ if (tty->termios->c_cflag & HUPCL)
+ tty_port_lower_dtr_rts(&info->port);
+
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ info->port.flags &= ~ASYNC_INITIALIZED;
+
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ }
+
+#ifdef CY_DEBUG_OPEN
+ printk(KERN_DEBUG "cyc shutdown done\n");
+#endif
+} /* shutdown */
+
+/*
+ * ------------------------------------------------------------
+ * cy_open() and friends
+ * ------------------------------------------------------------
+ */
+
+/*
+ * 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;
+ unsigned int i, line;
+ int retval;
+
+ line = tty->index;
+ if (tty->index < 0 || NR_PORTS <= line)
+ return -ENODEV;
+
+ for (i = 0; i < NR_CARDS; i++)
+ if (line < cy_card[i].first_line + cy_card[i].nports &&
+ line >= cy_card[i].first_line)
+ break;
+ if (i >= NR_CARDS)
+ return -ENODEV;
+ info = &cy_card[i].ports[line - cy_card[i].first_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 (cy_is_Z(info->card)) {
+ struct cyclades_card *cinfo = info->card;
+ struct FIRM_ID __iomem *firm_id = cinfo->base_addr + ID_ADDRESS;
+
+ if (!cyz_is_loaded(cinfo)) {
+ if (cinfo->hw_ver == ZE_V1 && cyz_fpga_loaded(cinfo) &&
+ readl(&firm_id->signature) ==
+ ZFIRM_HLT) {
+ printk(KERN_ERR "cyc:Cyclades-Z Error: you "
+ "need an external power supply for "
+ "this number of ports.\nFirmware "
+ "halted.\n");
+ } else {
+ printk(KERN_ERR "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) {
+ u16 intr;
+
+ /* Enable interrupts on the PLX chip */
+ intr = readw(&cinfo->ctl_addr.p9060->
+ intr_ctrl_stat) | 0x0900;
+ cy_writew(&cinfo->ctl_addr.p9060->
+ intr_ctrl_stat, intr);
+ /* Enable interrupts on the FW */
+ retval = cyz_issue_cmd(cinfo, 0,
+ C_CM_IRQ_ENBL, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:IRQ enable retval "
+ "was %x\n", retval);
+ }
+ 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(KERN_DEBUG "cyc:cy_open ttyC%d\n", info->line);
+#endif
+ tty->driver_data = info;
+ if (serial_paranoia_check(info, tty->name, "cy_open"))
+ return -ENODEV;
+
+#ifdef CY_DEBUG_OPEN
+ printk(KERN_DEBUG "cyc:cy_open ttyC%d, count = %d\n", info->line,
+ info->port.count);
+#endif
+ info->port.count++;
+#ifdef CY_DEBUG_COUNT
+ printk(KERN_DEBUG "cyc:cy_open (%d): incrementing count to %d\n",
+ current->pid, info->port.count);
+#endif
+
+ /*
+ * If the port is the middle of closing, bail out now
+ */
+ if (tty_hung_up_p(filp) || (info->port.flags & ASYNC_CLOSING)) {
+ wait_event_interruptible_tty(info->port.close_wait,
+ !(info->port.flags & ASYNC_CLOSING));
+ return (info->port.flags & ASYNC_HUP_NOTIFY) ? -EAGAIN: -ERESTARTSYS;
+ }
+
+ /*
+ * Start up serial port
+ */
+ retval = cy_startup(info, tty);
+ if (retval)
+ return retval;
+
+ retval = tty_port_block_til_ready(&info->port, tty, filp);
+ if (retval) {
+#ifdef CY_DEBUG_OPEN
+ printk(KERN_DEBUG "cyc:cy_open returning after block_til_ready "
+ "with %d\n", retval);
+#endif
+ return retval;
+ }
+
+ info->throttle = 0;
+ tty_port_tty_set(&info->port, tty);
+
+#ifdef CY_DEBUG_OPEN
+ printk(KERN_DEBUG "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_card *card;
+ struct cyclades_port *info = tty->driver_data;
+ 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;
+
+ card = info->card;
+ if (!cy_is_Z(card)) {
+ while (cyy_readb(info, CySRER) & CyTxRdy) {
+ if (msleep_interruptible(jiffies_to_msecs(char_time)))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies +
+ timeout))
+ break;
+ }
+ }
+ /* Run one more char cycle */
+ msleep_interruptible(jiffies_to_msecs(char_time * 5));
+}
+
+static void cy_flush_buffer(struct tty_struct *tty)
+{
+ struct cyclades_port *info = tty->driver_data;
+ struct cyclades_card *card;
+ int channel, retval;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_IO
+ printk(KERN_DEBUG "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 - card->first_line;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ if (cy_is_Z(card)) { /* If it is a Z card, flush the on-board
+ buffers as well */
+ spin_lock_irqsave(&card->card_lock, flags);
+ retval = cyz_issue_cmd(card, channel, C_CM_FLUSH_TX, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc: flush_buffer retval on ttyC%d "
+ "was %x\n", info->line, retval);
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ }
+ tty_wakeup(tty);
+} /* cy_flush_buffer */
+
+
+static void cy_do_close(struct tty_port *port)
+{
+ struct cyclades_port *info = container_of(port, struct cyclades_port,
+ port);
+ struct cyclades_card *card;
+ unsigned long flags;
+ int channel;
+
+ card = info->card;
+ channel = info->line - card->first_line;
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ if (!cy_is_Z(card)) {
+ /* Stop accepting input */
+ cyy_writeb(info, CyCAR, channel & 0x03);
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) & ~CyRxData);
+ if (info->port.flags & ASYNC_INITIALIZED) {
+ /* Waiting for on-board buffers to be empty before
+ closing the port */
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ cy_wait_until_sent(port->tty, info->timeout);
+ spin_lock_irqsave(&card->card_lock, flags);
+ }
+ } else {
+#ifdef Z_WAKE
+ /* Waiting for on-board buffers to be empty before closing
+ the port */
+ struct CH_CTRL __iomem *ch_ctrl = info->u.cyz.ch_ctrl;
+ int retval;
+
+ if (readl(&ch_ctrl->flow_status) != C_FS_TXIDLE) {
+ retval = cyz_issue_cmd(card, channel, C_CM_IOCTLW, 0L);
+ if (retval != 0) {
+ printk(KERN_DEBUG "cyc:cy_close retval on "
+ "ttyC%d was %x\n", info->line, retval);
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ wait_for_completion_interruptible(&info->shutdown_wait);
+ spin_lock_irqsave(&card->card_lock, flags);
+ }
+#endif
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ cy_shutdown(info, port->tty);
+}
+
+/*
+ * 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 = tty->driver_data;
+ if (!info || serial_paranoia_check(info, tty->name, "cy_close"))
+ return;
+ tty_port_close(&info->port, tty, filp);
+} /* 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 = tty->driver_data;
+ unsigned long flags;
+ int c, ret = 0;
+
+#ifdef CY_DEBUG_IO
+ printk(KERN_DEBUG "cyc:cy_write ttyC%d\n", info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_write"))
+ return 0;
+
+ if (!info->port.xmit_buf)
+ return 0;
+
+ spin_lock_irqsave(&info->card->card_lock, flags);
+ while (1) {
+ c = min(count, (int)(SERIAL_XMIT_SIZE - info->xmit_cnt - 1));
+ c = min(c, (int)(SERIAL_XMIT_SIZE - info->xmit_head));
+
+ if (c <= 0)
+ break;
+
+ memcpy(info->port.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;
+ }
+ spin_unlock_irqrestore(&info->card->card_lock, 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 int cy_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct cyclades_port *info = tty->driver_data;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_IO
+ printk(KERN_DEBUG "cyc:cy_put_char ttyC%d\n", info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_put_char"))
+ return 0;
+
+ if (!info->port.xmit_buf)
+ return 0;
+
+ spin_lock_irqsave(&info->card->card_lock, flags);
+ if (info->xmit_cnt >= (int)(SERIAL_XMIT_SIZE - 1)) {
+ spin_unlock_irqrestore(&info->card->card_lock, flags);
+ return 0;
+ }
+
+ info->port.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;
+ spin_unlock_irqrestore(&info->card->card_lock, flags);
+ return 1;
+} /* 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 = tty->driver_data;
+
+#ifdef CY_DEBUG_IO
+ printk(KERN_DEBUG "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->port.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 = tty->driver_data;
+ int ret;
+
+#ifdef CY_DEBUG_IO
+ printk(KERN_DEBUG "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 = tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "cy_chars_in_buffer"))
+ return 0;
+
+#ifdef Z_EXT_CHARS_IN_BUFFER
+ if (!cy_is_Z(info->card)) {
+#endif /* Z_EXT_CHARS_IN_BUFFER */
+#ifdef CY_DEBUG_IO
+ printk(KERN_DEBUG "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 {
+ struct BUF_CTRL __iomem *buf_ctrl = info->u.cyz.buf_ctrl;
+ int char_count;
+ __u32 tx_put, tx_get, tx_bufsize;
+
+ tx_get = readl(&buf_ctrl->tx_get);
+ tx_put = readl(&buf_ctrl->tx_put);
+ tx_bufsize = 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(KERN_DEBUG "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, __u32 baud)
+{
+ int co, co_val, bpr;
+ __u32 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 cy_set_line_char(struct cyclades_port *info, struct tty_struct *tty)
+{
+ struct cyclades_card *card;
+ unsigned long flags;
+ int channel;
+ unsigned cflag, iflag;
+ int baud, baud_rate = 0;
+ int i;
+
+ if (!tty->termios) /* XXX can this happen at all? */
+ return;
+
+ if (info->line == -1)
+ return;
+
+ cflag = tty->termios->c_cflag;
+ iflag = tty->termios->c_iflag;
+
+ /*
+ * Set up the tty->alt_speed kludge
+ */
+ if ((info->port.flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ tty->alt_speed = 57600;
+ if ((info->port.flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ tty->alt_speed = 115200;
+ if ((info->port.flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ tty->alt_speed = 230400;
+ if ((info->port.flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ tty->alt_speed = 460800;
+
+ card = info->card;
+ channel = info->line - card->first_line;
+
+ if (!cy_is_Z(card)) {
+ u32 cflags;
+
+ /* baud rate */
+ baud = tty_get_baud_rate(tty);
+ if (baud == 38400 && (info->port.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->port.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->port.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;
+ /* receive threshold */
+ info->cor3 = (info->default_threshold ?
+ info->default_threshold : baud_cor3[i]);
+ 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->port.flags |= ASYNC_CTS_FLOW;
+ info->cor2 |= CyCtsAE;
+ } else {
+ info->port.flags &= ~ASYNC_CTS_FLOW;
+ info->cor2 &= ~CyCtsAE;
+ }
+ if (cflag & CLOCAL)
+ info->port.flags &= ~ASYNC_CHECK_CD;
+ else
+ info->port.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 &= 0x03;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_writeb(info, CyCAR, channel);
+
+ /* tx and rx baud rate */
+
+ cyy_writeb(info, CyTCOR, info->tco);
+ cyy_writeb(info, CyTBPR, info->tbpr);
+ cyy_writeb(info, CyRCOR, info->rco);
+ cyy_writeb(info, CyRBPR, info->rbpr);
+
+ /* set line characteristics according configuration */
+
+ cyy_writeb(info, CySCHR1, START_CHAR(tty));
+ cyy_writeb(info, CySCHR2, STOP_CHAR(tty));
+ cyy_writeb(info, CyCOR1, info->cor1);
+ cyy_writeb(info, CyCOR2, info->cor2);
+ cyy_writeb(info, CyCOR3, info->cor3);
+ cyy_writeb(info, CyCOR4, info->cor4);
+ cyy_writeb(info, CyCOR5, info->cor5);
+
+ cyy_issue_cmd(info, CyCOR_CHANGE | CyCOR1ch | CyCOR2ch |
+ CyCOR3ch);
+
+ /* !!! Is this needed? */
+ cyy_writeb(info, CyCAR, channel);
+ cyy_writeb(info, CyRTPR,
+ (info->default_timeout ? info->default_timeout : 0x02));
+ /* 10ms rx timeout */
+
+ cflags = CyCTS;
+ if (!C_CLOCAL(tty))
+ cflags |= CyDSR | CyRI | CyDCD;
+ /* without modem intr */
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) | CyMdmCh);
+ /* act on 1->0 modem transitions */
+ if ((cflag & CRTSCTS) && info->rflow)
+ cyy_writeb(info, CyMCOR1, cflags | rflow_thr[i]);
+ else
+ cyy_writeb(info, CyMCOR1, cflags);
+ /* act on 0->1 modem transitions */
+ cyy_writeb(info, CyMCOR2, cflags);
+
+ if (i == 0) /* baud rate is zero, turn off line */
+ cyy_change_rts_dtr(info, 0, TIOCM_DTR);
+ else
+ cyy_change_rts_dtr(info, TIOCM_DTR, 0);
+
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ } else {
+ struct CH_CTRL __iomem *ch_ctrl = info->u.cyz.ch_ctrl;
+ __u32 sw_flow;
+ int retval;
+
+ if (!cyz_is_loaded(card))
+ return;
+
+ /* baud rate */
+ baud = tty_get_baud_rate(tty);
+ if (baud == 38400 && (info->port.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->port.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,
+ readl(&ch_ctrl->comm_data_l) | C_DL_2STOP);
+ } else {
+ cy_writel(&ch_ctrl->comm_data_l,
+ 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,
+ readl(&ch_ctrl->hw_flow) | C_RS_CTS | C_RS_RTS);
+ } else {
+ cy_writel(&ch_ctrl->hw_flow, 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->port.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(card, channel, C_CM_IOCTL, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:set_line_char retval on ttyC%d "
+ "was %x\n", info->line, retval);
+ }
+
+ /* CD sensitivity */
+ if (cflag & CLOCAL)
+ info->port.flags &= ~ASYNC_CHECK_CD;
+ else
+ info->port.flags |= ASYNC_CHECK_CD;
+
+ if (baud == 0) { /* baud rate is zero, turn off line */
+ cy_writel(&ch_ctrl->rs_control,
+ readl(&ch_ctrl->rs_control) & ~C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "cyc:set_line_char dropping Z DTR\n");
+#endif
+ } else {
+ cy_writel(&ch_ctrl->rs_control,
+ readl(&ch_ctrl->rs_control) | C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "cyc:set_line_char raising Z DTR\n");
+#endif
+ }
+
+ retval = cyz_issue_cmd(card, channel, C_CM_IOCTLM, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:set_line_char(2) retval on ttyC%d "
+ "was %x\n", info->line, retval);
+ }
+
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+ }
+} /* set_line_char */
+
+static int cy_get_serial_info(struct cyclades_port *info,
+ struct serial_struct __user *retinfo)
+{
+ struct cyclades_card *cinfo = info->card;
+ struct serial_struct tmp = {
+ .type = info->type,
+ .line = info->line,
+ .port = (info->card - cy_card) * 0x100 + info->line -
+ cinfo->first_line,
+ .irq = cinfo->irq,
+ .flags = info->port.flags,
+ .close_delay = info->port.close_delay,
+ .closing_wait = info->port.closing_wait,
+ .baud_base = info->baud,
+ .custom_divisor = info->custom_divisor,
+ .hub6 = 0, /*!!! */
+ };
+ return copy_to_user(retinfo, &tmp, sizeof(*retinfo)) ? -EFAULT : 0;
+}
+
+static int
+cy_set_serial_info(struct cyclades_port *info, struct tty_struct *tty,
+ struct serial_struct __user *new_info)
+{
+ struct serial_struct new_serial;
+ int ret;
+
+ if (copy_from_user(&new_serial, new_info, sizeof(new_serial)))
+ return -EFAULT;
+
+ mutex_lock(&info->port.mutex);
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (new_serial.close_delay != info->port.close_delay ||
+ new_serial.baud_base != info->baud ||
+ (new_serial.flags & ASYNC_FLAGS &
+ ~ASYNC_USR_MASK) !=
+ (info->port.flags & ASYNC_FLAGS & ~ASYNC_USR_MASK))
+ {
+ mutex_unlock(&info->port.mutex);
+ return -EPERM;
+ }
+ info->port.flags = (info->port.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->port.flags = (info->port.flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS);
+ info->port.close_delay = new_serial.close_delay * HZ / 100;
+ info->port.closing_wait = new_serial.closing_wait * HZ / 100;
+
+check_and_exit:
+ if (info->port.flags & ASYNC_INITIALIZED) {
+ cy_set_line_char(info, tty);
+ ret = 0;
+ } else {
+ ret = cy_startup(info, tty);
+ }
+ mutex_unlock(&info->port.mutex);
+ return ret;
+} /* 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)
+{
+ struct cyclades_card *card = info->card;
+ unsigned int result;
+ unsigned long flags;
+ u8 status;
+
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ status = cyy_readb(info, CySRER) & (CyTxRdy | CyTxMpty);
+ spin_unlock_irqrestore(&card->card_lock, 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 cyclades_port *info = tty->driver_data;
+ struct cyclades_card *card;
+ int result;
+
+ if (serial_paranoia_check(info, tty->name, __func__))
+ return -ENODEV;
+
+ card = info->card;
+
+ if (!cy_is_Z(card)) {
+ unsigned long flags;
+ int channel = info->line - card->first_line;
+ u8 status;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_writeb(info, CyCAR, channel & 0x03);
+ status = cyy_readb(info, CyMSVR1);
+ status |= cyy_readb(info, CyMSVR2);
+ spin_unlock_irqrestore(&card->card_lock, 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 {
+ u32 lstatus;
+
+ if (!cyz_is_loaded(card)) {
+ result = -ENODEV;
+ goto end;
+ }
+
+ lstatus = readl(&info->u.cyz.ch_ctrl->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);
+ }
+end:
+ return result;
+} /* cy_tiomget */
+
+static int
+cy_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct cyclades_port *info = tty->driver_data;
+ struct cyclades_card *card;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, __func__))
+ return -ENODEV;
+
+ card = info->card;
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_change_rts_dtr(info, set, clear);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ } else {
+ struct CH_CTRL __iomem *ch_ctrl = info->u.cyz.ch_ctrl;
+ int retval, channel = info->line - card->first_line;
+ u32 rs;
+
+ if (!cyz_is_loaded(card))
+ return -ENODEV;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ rs = readl(&ch_ctrl->rs_control);
+ if (set & TIOCM_RTS)
+ rs |= C_RS_RTS;
+ if (clear & TIOCM_RTS)
+ rs &= ~C_RS_RTS;
+ if (set & TIOCM_DTR) {
+ rs |= C_RS_DTR;
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "cyc:set_modem_info raising Z DTR\n");
+#endif
+ }
+ if (clear & TIOCM_DTR) {
+ rs &= ~C_RS_DTR;
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "cyc:set_modem_info clearing "
+ "Z DTR\n");
+#endif
+ }
+ cy_writel(&ch_ctrl->rs_control, rs);
+ retval = cyz_issue_cmd(card, channel, C_CM_IOCTLM, 0L);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:set_modem_info retval on ttyC%d "
+ "was %x\n", info->line, retval);
+ }
+ }
+ return 0;
+}
+
+/*
+ * cy_break() --- routine which turns the break handling on or off
+ */
+static int cy_break(struct tty_struct *tty, int break_state)
+{
+ struct cyclades_port *info = tty->driver_data;
+ struct cyclades_card *card;
+ unsigned long flags;
+ int retval = 0;
+
+ if (serial_paranoia_check(info, tty->name, "cy_break"))
+ return -EINVAL;
+
+ card = info->card;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (!cy_is_Z(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) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ start_xmit(info);
+ spin_lock_irqsave(&card->card_lock, flags);
+ }
+ }
+ } else {
+ if (!info->breakoff) {
+ info->breakoff = 1;
+ if (!info->xmit_cnt) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ start_xmit(info);
+ spin_lock_irqsave(&card->card_lock, flags);
+ }
+ }
+ }
+ } else {
+ if (break_state == -1) {
+ retval = cyz_issue_cmd(card,
+ info->line - card->first_line,
+ C_CM_SET_BREAK, 0L);
+ if (retval != 0) {
+ printk(KERN_ERR "cyc:cy_break (set) retval on "
+ "ttyC%d was %x\n", info->line, retval);
+ }
+ } else {
+ retval = cyz_issue_cmd(card,
+ info->line - card->first_line,
+ C_CM_CLR_BREAK, 0L);
+ if (retval != 0) {
+ printk(KERN_DEBUG "cyc:cy_break (clr) retval "
+ "on ttyC%d was %x\n", info->line,
+ retval);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ return retval;
+} /* cy_break */
+
+static int set_threshold(struct cyclades_port *info, unsigned long value)
+{
+ struct cyclades_card *card = info->card;
+ unsigned long flags;
+
+ if (!cy_is_Z(card)) {
+ info->cor3 &= ~CyREC_FIFO;
+ info->cor3 |= value & CyREC_FIFO;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_writeb(info, CyCOR3, info->cor3);
+ cyy_issue_cmd(info, CyCOR_CHANGE | CyCOR3ch);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ }
+ return 0;
+} /* set_threshold */
+
+static int get_threshold(struct cyclades_port *info,
+ unsigned long __user *value)
+{
+ struct cyclades_card *card = info->card;
+
+ if (!cy_is_Z(card)) {
+ u8 tmp = cyy_readb(info, CyCOR3) & CyREC_FIFO;
+ return put_user(tmp, value);
+ }
+ return 0;
+} /* get_threshold */
+
+static int set_timeout(struct cyclades_port *info, unsigned long value)
+{
+ struct cyclades_card *card = info->card;
+ unsigned long flags;
+
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_writeb(info, CyRTPR, value & 0xff);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ }
+ return 0;
+} /* set_timeout */
+
+static int get_timeout(struct cyclades_port *info,
+ unsigned long __user *value)
+{
+ struct cyclades_card *card = info->card;
+
+ if (!cy_is_Z(card)) {
+ u8 tmp = cyy_readb(info, CyRTPR);
+ return put_user(tmp, value);
+ }
+ return 0;
+} /* get_timeout */
+
+static int cy_cflags_changed(struct cyclades_port *info, unsigned long arg,
+ struct cyclades_icount *cprev)
+{
+ struct cyclades_icount cnow;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&info->card->card_lock, flags);
+ cnow = info->icount; /* atomic copy */
+ spin_unlock_irqrestore(&info->card->card_lock, flags);
+
+ ret = ((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));
+
+ *cprev = cnow;
+
+ return ret;
+}
+
+/*
+ * 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,
+ unsigned int cmd, unsigned long arg)
+{
+ struct cyclades_port *info = tty->driver_data;
+ struct cyclades_icount cnow; /* kernel counter temps */
+ 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(KERN_DEBUG "cyc:cy_ioctl ttyC%d, cmd = %x arg = %lx\n",
+ info->line, cmd, arg);
+#endif
+
+ switch (cmd) {
+ case CYGETMON:
+ if (copy_to_user(argp, &info->mon, sizeof(info->mon))) {
+ ret_val = -EFAULT;
+ break;
+ }
+ memset(&info->mon, 0, sizeof(info->mon));
+ break;
+ case CYGETTHRESH:
+ ret_val = get_threshold(info, argp);
+ break;
+ case CYSETTHRESH:
+ ret_val = set_threshold(info, arg);
+ break;
+ case CYGETDEFTHRESH:
+ ret_val = put_user(info->default_threshold,
+ (unsigned long __user *)argp);
+ break;
+ case CYSETDEFTHRESH:
+ info->default_threshold = arg & 0x0f;
+ break;
+ case CYGETTIMEOUT:
+ ret_val = get_timeout(info, argp);
+ break;
+ case CYSETTIMEOUT:
+ ret_val = set_timeout(info, arg);
+ break;
+ case CYGETDEFTIMEOUT:
+ ret_val = put_user(info->default_timeout,
+ (unsigned long __user *)argp);
+ break;
+ case CYSETDEFTIMEOUT:
+ info->default_timeout = arg & 0xff;
+ break;
+ case CYSETRFLOW:
+ info->rflow = (int)arg;
+ break;
+ case CYGETRFLOW:
+ ret_val = info->rflow;
+ break;
+ case CYSETRTSDTR_INV:
+ info->rtsdtr_inv = (int)arg;
+ break;
+ case CYGETRTSDTR_INV:
+ ret_val = info->rtsdtr_inv;
+ break;
+ case CYGETCD1400VER:
+ ret_val = info->chip_rev;
+ break;
+#ifndef CONFIG_CYZ_INTR
+ case CYZSETPOLLCYCLE:
+ cyz_polling_cycle = (arg * HZ) / 1000;
+ break;
+ case CYZGETPOLLCYCLE:
+ ret_val = (cyz_polling_cycle * 1000) / HZ;
+ break;
+#endif /* CONFIG_CYZ_INTR */
+ case CYSETWAIT:
+ info->port.closing_wait = (unsigned short)arg * HZ / 100;
+ break;
+ case CYGETWAIT:
+ ret_val = info->port.closing_wait / (HZ / 100);
+ break;
+ case TIOCGSERIAL:
+ ret_val = cy_get_serial_info(info, argp);
+ break;
+ case TIOCSSERIAL:
+ ret_val = cy_set_serial_info(info, tty, 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:
+ spin_lock_irqsave(&info->card->card_lock, flags);
+ /* note the counters on entry */
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->card->card_lock, flags);
+ ret_val = wait_event_interruptible(info->port.delta_msr_wait,
+ cy_cflags_changed(info, arg, &cnow));
+ 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.
+ */
+ default:
+ ret_val = -ENOIOCTLCMD;
+ }
+
+#ifdef CY_DEBUG_OTHER
+ printk(KERN_DEBUG "cyc:cy_ioctl done\n");
+#endif
+ return ret_val;
+} /* cy_ioctl */
+
+static int cy_get_icount(struct tty_struct *tty,
+ struct serial_icounter_struct *sic)
+{
+ struct cyclades_port *info = tty->driver_data;
+ struct cyclades_icount cnow; /* Used to snapshot */
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->card->card_lock, flags);
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->card->card_lock, flags);
+
+ sic->cts = cnow.cts;
+ sic->dsr = cnow.dsr;
+ sic->rng = cnow.rng;
+ sic->dcd = cnow.dcd;
+ sic->rx = cnow.rx;
+ sic->tx = cnow.tx;
+ sic->frame = cnow.frame;
+ sic->overrun = cnow.overrun;
+ sic->parity = cnow.parity;
+ sic->brk = cnow.brk;
+ sic->buf_overrun = cnow.buf_overrun;
+ return 0;
+}
+
+/*
+ * 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 ktermios *old_termios)
+{
+ struct cyclades_port *info = tty->driver_data;
+
+#ifdef CY_DEBUG_OTHER
+ printk(KERN_DEBUG "cyc:cy_set_termios ttyC%d\n", info->line);
+#endif
+
+ cy_set_line_char(info, tty);
+
+ 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->port.open_wait);
+#endif
+} /* 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 = tty->driver_data;
+ struct cyclades_card *card;
+ int 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 - card->first_line;
+
+ if (cy_is_Z(card)) {
+ if (ch == STOP_CHAR(tty))
+ cyz_issue_cmd(card, channel, C_CM_SENDXOFF, 0L);
+ else if (ch == START_CHAR(tty))
+ cyz_issue_cmd(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 = tty->driver_data;
+ struct cyclades_card *card;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_THROTTLE
+ char buf[64];
+
+ printk(KERN_DEBUG "cyc:throttle %s: %ld...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 (!cy_is_Z(card))
+ cy_send_xchar(tty, STOP_CHAR(tty));
+ else
+ info->throttle = 1;
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_change_rts_dtr(info, 0, TIOCM_RTS);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ } else {
+ info->throttle = 1;
+ }
+ }
+} /* 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 = tty->driver_data;
+ struct cyclades_card *card;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_THROTTLE
+ char buf[64];
+
+ printk(KERN_DEBUG "cyc:unthrottle %s: %ld...ttyC%d\n",
+ tty_name(tty, buf), tty_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;
+ if (!cy_is_Z(card)) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ cyy_change_rts_dtr(info, TIOCM_RTS, 0);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ } else {
+ info->throttle = 0;
+ }
+ }
+} /* 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 = tty->driver_data;
+ int channel;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+ printk(KERN_DEBUG "cyc:cy_stop ttyC%d\n", info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_stop"))
+ return;
+
+ cinfo = info->card;
+ channel = info->line - cinfo->first_line;
+ if (!cy_is_Z(cinfo)) {
+ spin_lock_irqsave(&cinfo->card_lock, flags);
+ cyy_writeb(info, CyCAR, channel & 0x03);
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) & ~CyTxRdy);
+ spin_unlock_irqrestore(&cinfo->card_lock, flags);
+ }
+} /* cy_stop */
+
+static void cy_start(struct tty_struct *tty)
+{
+ struct cyclades_card *cinfo;
+ struct cyclades_port *info = tty->driver_data;
+ int channel;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+ printk(KERN_DEBUG "cyc:cy_start ttyC%d\n", info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_start"))
+ return;
+
+ cinfo = info->card;
+ channel = info->line - cinfo->first_line;
+ if (!cy_is_Z(cinfo)) {
+ spin_lock_irqsave(&cinfo->card_lock, flags);
+ cyy_writeb(info, CyCAR, channel & 0x03);
+ cyy_writeb(info, CySRER, cyy_readb(info, CySRER) | CyTxRdy);
+ spin_unlock_irqrestore(&cinfo->card_lock, flags);
+ }
+} /* cy_start */
+
+/*
+ * cy_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void cy_hangup(struct tty_struct *tty)
+{
+ struct cyclades_port *info = tty->driver_data;
+
+#ifdef CY_DEBUG_OTHER
+ printk(KERN_DEBUG "cyc:cy_hangup ttyC%d\n", info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_hangup"))
+ return;
+
+ cy_flush_buffer(tty);
+ cy_shutdown(info, tty);
+ tty_port_hangup(&info->port);
+} /* cy_hangup */
+
+static int cyy_carrier_raised(struct tty_port *port)
+{
+ struct cyclades_port *info = container_of(port, struct cyclades_port,
+ port);
+ struct cyclades_card *cinfo = info->card;
+ unsigned long flags;
+ int channel = info->line - cinfo->first_line;
+ u32 cd;
+
+ spin_lock_irqsave(&cinfo->card_lock, flags);
+ cyy_writeb(info, CyCAR, channel & 0x03);
+ cd = cyy_readb(info, CyMSVR1) & CyDCD;
+ spin_unlock_irqrestore(&cinfo->card_lock, flags);
+
+ return cd;
+}
+
+static void cyy_dtr_rts(struct tty_port *port, int raise)
+{
+ struct cyclades_port *info = container_of(port, struct cyclades_port,
+ port);
+ struct cyclades_card *cinfo = info->card;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cinfo->card_lock, flags);
+ cyy_change_rts_dtr(info, raise ? TIOCM_RTS | TIOCM_DTR : 0,
+ raise ? 0 : TIOCM_RTS | TIOCM_DTR);
+ spin_unlock_irqrestore(&cinfo->card_lock, flags);
+}
+
+static int cyz_carrier_raised(struct tty_port *port)
+{
+ struct cyclades_port *info = container_of(port, struct cyclades_port,
+ port);
+
+ return readl(&info->u.cyz.ch_ctrl->rs_status) & C_RS_DCD;
+}
+
+static void cyz_dtr_rts(struct tty_port *port, int raise)
+{
+ struct cyclades_port *info = container_of(port, struct cyclades_port,
+ port);
+ struct cyclades_card *cinfo = info->card;
+ struct CH_CTRL __iomem *ch_ctrl = info->u.cyz.ch_ctrl;
+ int ret, channel = info->line - cinfo->first_line;
+ u32 rs;
+
+ rs = readl(&ch_ctrl->rs_control);
+ if (raise)
+ rs |= C_RS_RTS | C_RS_DTR;
+ else
+ rs &= ~(C_RS_RTS | C_RS_DTR);
+ cy_writel(&ch_ctrl->rs_control, rs);
+ ret = cyz_issue_cmd(cinfo, channel, C_CM_IOCTLM, 0L);
+ if (ret != 0)
+ printk(KERN_ERR "%s: retval on ttyC%d was %x\n",
+ __func__, info->line, ret);
+#ifdef CY_DEBUG_DTR
+ printk(KERN_DEBUG "%s: raising Z DTR\n", __func__);
+#endif
+}
+
+static const struct tty_port_operations cyy_port_ops = {
+ .carrier_raised = cyy_carrier_raised,
+ .dtr_rts = cyy_dtr_rts,
+ .shutdown = cy_do_close,
+};
+
+static const struct tty_port_operations cyz_port_ops = {
+ .carrier_raised = cyz_carrier_raised,
+ .dtr_rts = cyz_dtr_rts,
+ .shutdown = cy_do_close,
+};
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_init() and friends
+ *
+ * cy_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+static int __devinit cy_init_card(struct cyclades_card *cinfo)
+{
+ struct cyclades_port *info;
+ unsigned int channel, port;
+
+ spin_lock_init(&cinfo->card_lock);
+ cinfo->intr_enabled = 0;
+
+ cinfo->ports = kcalloc(cinfo->nports, sizeof(*cinfo->ports),
+ GFP_KERNEL);
+ if (cinfo->ports == NULL) {
+ printk(KERN_ERR "Cyclades: cannot allocate ports\n");
+ return -ENOMEM;
+ }
+
+ for (channel = 0, port = cinfo->first_line; channel < cinfo->nports;
+ channel++, port++) {
+ info = &cinfo->ports[channel];
+ tty_port_init(&info->port);
+ info->magic = CYCLADES_MAGIC;
+ info->card = cinfo;
+ info->line = port;
+
+ info->port.closing_wait = CLOSING_WAIT_DELAY;
+ info->port.close_delay = 5 * HZ / 10;
+ info->port.flags = STD_COM_FLAGS;
+ init_completion(&info->shutdown_wait);
+
+ if (cy_is_Z(cinfo)) {
+ struct FIRM_ID *firm_id = cinfo->base_addr + ID_ADDRESS;
+ struct ZFW_CTRL *zfw_ctrl;
+
+ info->port.ops = &cyz_port_ops;
+ info->type = PORT_STARTECH;
+
+ zfw_ctrl = cinfo->base_addr +
+ (readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ info->u.cyz.ch_ctrl = &zfw_ctrl->ch_ctrl[channel];
+ info->u.cyz.buf_ctrl = &zfw_ctrl->buf_ctrl[channel];
+
+ if (cinfo->hw_ver == ZO_V1)
+ info->xmit_fifo_size = CYZ_FIFO_SIZE;
+ else
+ info->xmit_fifo_size = 4 * CYZ_FIFO_SIZE;
+#ifdef CONFIG_CYZ_INTR
+ setup_timer(&cyz_rx_full_timer[port],
+ cyz_rx_restart, (unsigned long)info);
+#endif
+ } else {
+ unsigned short chip_number;
+ int index = cinfo->bus_index;
+
+ info->port.ops = &cyy_port_ops;
+ info->type = PORT_CIRRUS;
+ 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 */
+
+ chip_number = channel / CyPORTS_PER_CHIP;
+ info->u.cyy.base_addr = cinfo->base_addr +
+ (cy_chip_offset[chip_number] << index);
+ info->chip_rev = cyy_readb(info, CyGFRCR);
+
+ if (info->chip_rev >= 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->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->rtsdtr_inv = 0;
+ }
+ info->read_status_mask = CyTIMEOUT | CySPECHAR |
+ CyBREAK | CyPARITY | CyFRAME | CyOVERRUN;
+ }
+
+ }
+
+#ifndef CONFIG_CYZ_INTR
+ if (cy_is_Z(cinfo) && !timer_pending(&cyz_timerlist)) {
+ mod_timer(&cyz_timerlist, jiffies + 1);
+#ifdef CY_PCI_DEBUG
+ printk(KERN_DEBUG "Cyclades-Z polling initialized\n");
+#endif
+ }
+#endif
+ return 0;
+}
+
+/* initialize chips on Cyclom-Y card -- return number of valid
+ chips (which is number of ports/4) */
+static unsigned short __devinit 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 (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 && 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 (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 & (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 (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,
+ 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;
+ int isparam = 0;
+
+ nboard = 0;
+
+ /* 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;
+ }
+
+ /* 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_nocache(isa_address, CyISA_Ywin);
+ if (cy_isa_address == NULL) {
+ printk(KERN_ERR "Cyclom-Y/ISA: can't remap base "
+ "address\n");
+ continue;
+ }
+ cy_isa_nchan = CyPORTS_PER_CHIP *
+ cyy_init_card(cy_isa_address, 0);
+ if (cy_isa_nchan == 0) {
+ iounmap(cy_isa_address);
+ continue;
+ }
+
+ if (isparam && i < NR_CARDS && irq[i])
+ cy_isa_irq = irq[i];
+ else
+ /* find out the board's irq by probing */
+ cy_isa_irq = detect_isa_irq(cy_isa_address);
+ if (cy_isa_irq == 0) {
+ printk(KERN_ERR "Cyclom-Y/ISA found at 0x%lx, but the "
+ "IRQ could not be detected.\n",
+ (unsigned long)cy_isa_address);
+ iounmap(cy_isa_address);
+ continue;
+ }
+
+ if ((cy_next_channel + cy_isa_nchan) > NR_PORTS) {
+ printk(KERN_ERR "Cyclom-Y/ISA found at 0x%lx, but no "
+ "more channels are available. Change NR_PORTS "
+ "in cyclades.c and recompile kernel.\n",
+ (unsigned long)cy_isa_address);
+ iounmap(cy_isa_address);
+ return nboard;
+ }
+ /* fill the next cy_card structure available */
+ for (j = 0; j < NR_CARDS; j++) {
+ if (cy_card[j].base_addr == NULL)
+ break;
+ }
+ if (j == NR_CARDS) { /* no more cy_cards available */
+ printk(KERN_ERR "Cyclom-Y/ISA found at 0x%lx, but no "
+ "more cards can be used. Change NR_CARDS in "
+ "cyclades.c and recompile kernel.\n",
+ (unsigned long)cy_isa_address);
+ iounmap(cy_isa_address);
+ return nboard;
+ }
+
+ /* allocate IRQ */
+ if (request_irq(cy_isa_irq, cyy_interrupt,
+ 0, "Cyclom-Y", &cy_card[j])) {
+ printk(KERN_ERR "Cyclom-Y/ISA found at 0x%lx, but "
+ "could not allocate IRQ#%d.\n",
+ (unsigned long)cy_isa_address, cy_isa_irq);
+ iounmap(cy_isa_address);
+ return nboard;
+ }
+
+ /* set cy_card */
+ cy_card[j].base_addr = cy_isa_address;
+ cy_card[j].ctl_addr.p9050 = 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 / CyPORTS_PER_CHIP;
+ cy_card[j].nports = cy_isa_nchan;
+ if (cy_init_card(&cy_card[j])) {
+ cy_card[j].base_addr = NULL;
+ free_irq(cy_isa_irq, &cy_card[j]);
+ iounmap(cy_isa_address);
+ continue;
+ }
+ nboard++;
+
+ printk(KERN_INFO "Cyclom-Y/ISA #%d: 0x%lx-0x%lx, IRQ%d found: "
+ "%d channels starting from port %d\n",
+ j + 1, (unsigned long)cy_isa_address,
+ (unsigned long)(cy_isa_address + (CyISA_Ywin - 1)),
+ cy_isa_irq, cy_isa_nchan, cy_next_channel);
+
+ for (j = cy_next_channel;
+ j < cy_next_channel + cy_isa_nchan; j++)
+ tty_register_device(cy_serial_driver, j, NULL);
+ cy_next_channel += cy_isa_nchan;
+ }
+ return nboard;
+#else
+ return 0;
+#endif /* CONFIG_ISA */
+} /* cy_detect_isa */
+
+#ifdef CONFIG_PCI
+static inline int __devinit cyc_isfwstr(const char *str, unsigned int size)
+{
+ unsigned int a;
+
+ for (a = 0; a < size && *str; a++, str++)
+ if (*str & 0x80)
+ return -EINVAL;
+
+ for (; a < size; a++, str++)
+ if (*str)
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline void __devinit cyz_fpga_copy(void __iomem *fpga, const u8 *data,
+ unsigned int size)
+{
+ for (; size > 0; size--) {
+ cy_writel(fpga, *data++);
+ udelay(10);
+ }
+}
+
+static void __devinit plx_init(struct pci_dev *pdev, int irq,
+ struct RUNTIME_9060 __iomem *addr)
+{
+ /* Reset PLX */
+ cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) | 0x40000000);
+ udelay(100L);
+ cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) & ~0x40000000);
+
+ /* Reload Config. Registers from EEPROM */
+ cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) | 0x20000000);
+ udelay(100L);
+ cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) & ~0x20000000);
+
+ /* 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, irq);
+}
+
+static int __devinit __cyz_load_fw(const struct firmware *fw,
+ const char *name, const u32 mailbox, void __iomem *base,
+ void __iomem *fpga)
+{
+ const void *ptr = fw->data;
+ const struct zfile_header *h = ptr;
+ const struct zfile_config *c, *cs;
+ const struct zfile_block *b, *bs;
+ unsigned int a, tmp, len = fw->size;
+#define BAD_FW KERN_ERR "Bad firmware: "
+ if (len < sizeof(*h)) {
+ printk(BAD_FW "too short: %u<%zu\n", len, sizeof(*h));
+ return -EINVAL;
+ }
+
+ cs = ptr + h->config_offset;
+ bs = ptr + h->block_offset;
+
+ if ((void *)(cs + h->n_config) > ptr + len ||
+ (void *)(bs + h->n_blocks) > ptr + len) {
+ printk(BAD_FW "too short");
+ return -EINVAL;
+ }
+
+ if (cyc_isfwstr(h->name, sizeof(h->name)) ||
+ cyc_isfwstr(h->date, sizeof(h->date))) {
+ printk(BAD_FW "bad formatted header string\n");
+ return -EINVAL;
+ }
+
+ if (strncmp(name, h->name, sizeof(h->name))) {
+ printk(BAD_FW "bad name '%s' (expected '%s')\n", h->name, name);
+ return -EINVAL;
+ }
+
+ tmp = 0;
+ for (c = cs; c < cs + h->n_config; c++) {
+ for (a = 0; a < c->n_blocks; a++)
+ if (c->block_list[a] > h->n_blocks) {
+ printk(BAD_FW "bad block ref number in cfgs\n");
+ return -EINVAL;
+ }
+ if (c->mailbox == mailbox && c->function == 0) /* 0 is normal */
+ tmp++;
+ }
+ if (!tmp) {
+ printk(BAD_FW "nothing appropriate\n");
+ return -EINVAL;
+ }
+
+ for (b = bs; b < bs + h->n_blocks; b++)
+ if (b->file_offset + b->size > len) {
+ printk(BAD_FW "bad block data offset\n");
+ return -EINVAL;
+ }
+
+ /* everything is OK, let's seek'n'load it */
+ for (c = cs; c < cs + h->n_config; c++)
+ if (c->mailbox == mailbox && c->function == 0)
+ break;
+
+ for (a = 0; a < c->n_blocks; a++) {
+ b = &bs[c->block_list[a]];
+ if (b->type == ZBLOCK_FPGA) {
+ if (fpga != NULL)
+ cyz_fpga_copy(fpga, ptr + b->file_offset,
+ b->size);
+ } else {
+ if (base != NULL)
+ memcpy_toio(base + b->ram_offset,
+ ptr + b->file_offset, b->size);
+ }
+ }
+#undef BAD_FW
+ return 0;
+}
+
+static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr,
+ struct RUNTIME_9060 __iomem *ctl_addr, int irq)
+{
+ const struct firmware *fw;
+ struct FIRM_ID __iomem *fid = base_addr + ID_ADDRESS;
+ struct CUSTOM_REG __iomem *cust = base_addr;
+ struct ZFW_CTRL __iomem *pt_zfwctrl;
+ void __iomem *tmp;
+ u32 mailbox, status, nchan;
+ unsigned int i;
+ int retval;
+
+ retval = request_firmware(&fw, "cyzfirm.bin", &pdev->dev);
+ if (retval) {
+ dev_err(&pdev->dev, "can't get firmware\n");
+ goto err;
+ }
+
+ /* Check whether the firmware is already loaded and running. If
+ positive, skip this board */
+ if (__cyz_fpga_loaded(ctl_addr) && readl(&fid->signature) == ZFIRM_ID) {
+ u32 cntval = readl(base_addr + 0x190);
+
+ udelay(100);
+ if (cntval != readl(base_addr + 0x190)) {
+ /* FW counter is working, FW is running */
+ dev_dbg(&pdev->dev, "Cyclades-Z FW already loaded. "
+ "Skipping board.\n");
+ retval = 0;
+ goto err_rel;
+ }
+ }
+
+ /* start boot */
+ cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) &
+ ~0x00030800UL);
+
+ mailbox = readl(&ctl_addr->mail_box_0);
+
+ if (mailbox == 0 || __cyz_fpga_loaded(ctl_addr)) {
+ /* stops CPU and set window to beginning of RAM */
+ cy_writel(&ctl_addr->loc_addr_base, WIN_CREG);
+ cy_writel(&cust->cpu_stop, 0);
+ cy_writel(&ctl_addr->loc_addr_base, WIN_RAM);
+ udelay(100);
+ }
+
+ plx_init(pdev, irq, ctl_addr);
+
+ if (mailbox != 0) {
+ /* load FPGA */
+ retval = __cyz_load_fw(fw, "Cyclom-Z", mailbox, NULL,
+ base_addr);
+ if (retval)
+ goto err_rel;
+ if (!__cyz_fpga_loaded(ctl_addr)) {
+ dev_err(&pdev->dev, "fw upload successful, but fw is "
+ "not loaded\n");
+ goto err_rel;
+ }
+ }
+
+ /* stops CPU and set window to beginning of RAM */
+ cy_writel(&ctl_addr->loc_addr_base, WIN_CREG);
+ cy_writel(&cust->cpu_stop, 0);
+ cy_writel(&ctl_addr->loc_addr_base, WIN_RAM);
+ udelay(100);
+
+ /* clear memory */
+ for (tmp = base_addr; tmp < base_addr + RAM_SIZE; tmp++)
+ cy_writeb(tmp, 255);
+ if (mailbox != 0) {
+ /* set window to last 512K of RAM */
+ cy_writel(&ctl_addr->loc_addr_base, WIN_RAM + RAM_SIZE);
+ for (tmp = base_addr; tmp < base_addr + RAM_SIZE; tmp++)
+ cy_writeb(tmp, 255);
+ /* set window to beginning of RAM */
+ cy_writel(&ctl_addr->loc_addr_base, WIN_RAM);
+ }
+
+ retval = __cyz_load_fw(fw, "Cyclom-Z", mailbox, base_addr, NULL);
+ release_firmware(fw);
+ if (retval)
+ goto err;
+
+ /* finish boot and start boards */
+ cy_writel(&ctl_addr->loc_addr_base, WIN_CREG);
+ cy_writel(&cust->cpu_start, 0);
+ cy_writel(&ctl_addr->loc_addr_base, WIN_RAM);
+ i = 0;
+ while ((status = readl(&fid->signature)) != ZFIRM_ID && i++ < 40)
+ msleep(100);
+ if (status != ZFIRM_ID) {
+ if (status == ZFIRM_HLT) {
+ dev_err(&pdev->dev, "you need an external power supply "
+ "for this number of ports. Firmware halted and "
+ "board reset.\n");
+ retval = -EIO;
+ goto err;
+ }
+ dev_warn(&pdev->dev, "fid->signature = 0x%x... Waiting "
+ "some more time\n", status);
+ while ((status = readl(&fid->signature)) != ZFIRM_ID &&
+ i++ < 200)
+ msleep(100);
+ if (status != ZFIRM_ID) {
+ dev_err(&pdev->dev, "Board not started in 20 seconds! "
+ "Giving up. (fid->signature = 0x%x)\n",
+ status);
+ dev_info(&pdev->dev, "*** Warning ***: if you are "
+ "upgrading the FW, please power cycle the "
+ "system before loading the new FW to the "
+ "Cyclades-Z.\n");
+
+ if (__cyz_fpga_loaded(ctl_addr))
+ plx_init(pdev, irq, ctl_addr);
+
+ retval = -EIO;
+ goto err;
+ }
+ dev_dbg(&pdev->dev, "Firmware started after %d seconds.\n",
+ i / 10);
+ }
+ pt_zfwctrl = base_addr + readl(&fid->zfwctrl_addr);
+
+ dev_dbg(&pdev->dev, "fid=> %p, zfwctrl_addr=> %x, npt_zfwctrl=> %p\n",
+ base_addr + ID_ADDRESS, readl(&fid->zfwctrl_addr),
+ base_addr + readl(&fid->zfwctrl_addr));
+
+ nchan = readl(&pt_zfwctrl->board_ctrl.n_channel);
+ dev_info(&pdev->dev, "Cyclades-Z FW loaded: version = %x, ports = %u\n",
+ readl(&pt_zfwctrl->board_ctrl.fw_version), nchan);
+
+ if (nchan == 0) {
+ dev_warn(&pdev->dev, "no Cyclades-Z ports were found. Please "
+ "check the connection between the Z host card and the "
+ "serial expanders.\n");
+
+ if (__cyz_fpga_loaded(ctl_addr))
+ plx_init(pdev, irq, ctl_addr);
+
+ dev_info(&pdev->dev, "Null number of ports detected. Board "
+ "reset.\n");
+ retval = 0;
+ goto err;
+ }
+
+ cy_writel(&pt_zfwctrl->board_ctrl.op_system, C_OS_LINUX);
+ cy_writel(&pt_zfwctrl->board_ctrl.dr_version, DRIVER_VERSION);
+
+ /*
+ Early firmware failed to start looking for commands.
+ This enables firmware interrupts for those commands.
+ */
+ cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) |
+ (1 << 17));
+ cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) |
+ 0x00030800UL);
+
+ return nchan;
+err_rel:
+ release_firmware(fw);
+err:
+ return retval;
+}
+
+static int __devinit cy_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ void __iomem *addr0 = NULL, *addr2 = NULL;
+ char *card_name = NULL;
+ u32 uninitialized_var(mailbox);
+ unsigned int device_id, nchan = 0, card_no, i;
+ unsigned char plx_ver;
+ int retval, irq;
+
+ retval = pci_enable_device(pdev);
+ if (retval) {
+ dev_err(&pdev->dev, "cannot enable device\n");
+ goto err;
+ }
+
+ /* read PCI configuration area */
+ irq = pdev->irq;
+ device_id = pdev->device & ~PCI_DEVICE_ID_MASK;
+
+#if defined(__alpha__)
+ if (device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo) { /* below 1M? */
+ dev_err(&pdev->dev, "Cyclom-Y/PCI not supported for low "
+ "addresses on Alpha systems.\n");
+ retval = -EIO;
+ goto err_dis;
+ }
+#endif
+ if (device_id == PCI_DEVICE_ID_CYCLOM_Z_Lo) {
+ dev_err(&pdev->dev, "Cyclades-Z/PCI not supported for low "
+ "addresses\n");
+ retval = -EIO;
+ goto err_dis;
+ }
+
+ if (pci_resource_flags(pdev, 2) & IORESOURCE_IO) {
+ dev_warn(&pdev->dev, "PCI I/O bit incorrectly set. Ignoring "
+ "it...\n");
+ pdev->resource[2].flags &= ~IORESOURCE_IO;
+ }
+
+ retval = pci_request_regions(pdev, "cyclades");
+ if (retval) {
+ dev_err(&pdev->dev, "failed to reserve resources\n");
+ goto err_dis;
+ }
+
+ retval = -EIO;
+ if (device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo ||
+ device_id == PCI_DEVICE_ID_CYCLOM_Y_Hi) {
+ card_name = "Cyclom-Y";
+
+ addr0 = ioremap_nocache(pci_resource_start(pdev, 0),
+ CyPCI_Yctl);
+ if (addr0 == NULL) {
+ dev_err(&pdev->dev, "can't remap ctl region\n");
+ goto err_reg;
+ }
+ addr2 = ioremap_nocache(pci_resource_start(pdev, 2),
+ CyPCI_Ywin);
+ if (addr2 == NULL) {
+ dev_err(&pdev->dev, "can't remap base region\n");
+ goto err_unmap;
+ }
+
+ nchan = CyPORTS_PER_CHIP * cyy_init_card(addr2, 1);
+ if (nchan == 0) {
+ dev_err(&pdev->dev, "Cyclom-Y PCI host card with no "
+ "Serial-Modules\n");
+ goto err_unmap;
+ }
+ } else if (device_id == PCI_DEVICE_ID_CYCLOM_Z_Hi) {
+ struct RUNTIME_9060 __iomem *ctl_addr;
+
+ ctl_addr = addr0 = ioremap_nocache(pci_resource_start(pdev, 0),
+ CyPCI_Zctl);
+ if (addr0 == NULL) {
+ dev_err(&pdev->dev, "can't remap ctl region\n");
+ goto err_reg;
+ }
+
+ /* Disable interrupts on the PLX before resetting it */
+ cy_writew(&ctl_addr->intr_ctrl_stat,
+ readw(&ctl_addr->intr_ctrl_stat) & ~0x0900);
+
+ plx_init(pdev, irq, addr0);
+
+ mailbox = readl(&ctl_addr->mail_box_0);
+
+ addr2 = ioremap_nocache(pci_resource_start(pdev, 2),
+ mailbox == ZE_V1 ? CyPCI_Ze_win : CyPCI_Zwin);
+ if (addr2 == NULL) {
+ dev_err(&pdev->dev, "can't remap base region\n");
+ goto err_unmap;
+ }
+
+ if (mailbox == ZE_V1) {
+ card_name = "Cyclades-Ze";
+ } else {
+ card_name = "Cyclades-8Zo";
+#ifdef CY_PCI_DEBUG
+ if (mailbox == ZO_V1) {
+ cy_writel(&ctl_addr->loc_addr_base, WIN_CREG);
+ dev_info(&pdev->dev, "Cyclades-8Zo/PCI: FPGA "
+ "id %lx, ver %lx\n", (ulong)(0xff &
+ readl(&((struct CUSTOM_REG *)addr2)->
+ fpga_id)), (ulong)(0xff &
+ readl(&((struct CUSTOM_REG *)addr2)->
+ fpga_version)));
+ cy_writel(&ctl_addr->loc_addr_base, WIN_RAM);
+ } else {
+ dev_info(&pdev->dev, "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.
+ */
+ if ((mailbox == ZO_V1) || (mailbox == ZO_V2))
+ cy_writel(addr2 + ID_ADDRESS, 0L);
+ }
+
+ retval = cyz_load_fw(pdev, addr2, addr0, irq);
+ if (retval <= 0)
+ goto err_unmap;
+ nchan = retval;
+ }
+
+ if ((cy_next_channel + nchan) > NR_PORTS) {
+ dev_err(&pdev->dev, "Cyclades-8Zo/PCI found, but no "
+ "channels are available. Change NR_PORTS in "
+ "cyclades.c and recompile kernel.\n");
+ goto err_unmap;
+ }
+ /* fill the next cy_card structure available */
+ for (card_no = 0; card_no < NR_CARDS; card_no++) {
+ if (cy_card[card_no].base_addr == NULL)
+ break;
+ }
+ if (card_no == NR_CARDS) { /* no more cy_cards available */
+ dev_err(&pdev->dev, "Cyclades-8Zo/PCI found, but no "
+ "more cards can be used. Change NR_CARDS in "
+ "cyclades.c and recompile kernel.\n");
+ goto err_unmap;
+ }
+
+ if (device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo ||
+ device_id == PCI_DEVICE_ID_CYCLOM_Y_Hi) {
+ /* allocate IRQ */
+ retval = request_irq(irq, cyy_interrupt,
+ IRQF_SHARED, "Cyclom-Y", &cy_card[card_no]);
+ if (retval) {
+ dev_err(&pdev->dev, "could not allocate IRQ\n");
+ goto err_unmap;
+ }
+ cy_card[card_no].num_chips = nchan / CyPORTS_PER_CHIP;
+ } else {
+ struct FIRM_ID __iomem *firm_id = addr2 + ID_ADDRESS;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+
+ zfw_ctrl = addr2 + (readl(&firm_id->zfwctrl_addr) & 0xfffff);
+
+ cy_card[card_no].hw_ver = mailbox;
+ cy_card[card_no].num_chips = (unsigned int)-1;
+ cy_card[card_no].board_ctrl = &zfw_ctrl->board_ctrl;
+#ifdef CONFIG_CYZ_INTR
+ /* allocate IRQ only if board has an IRQ */
+ if (irq != 0 && irq != 255) {
+ retval = request_irq(irq, cyz_interrupt,
+ IRQF_SHARED, "Cyclades-Z",
+ &cy_card[card_no]);
+ if (retval) {
+ dev_err(&pdev->dev, "could not allocate IRQ\n");
+ goto err_unmap;
+ }
+ }
+#endif /* CONFIG_CYZ_INTR */
+ }
+
+ /* set cy_card */
+ cy_card[card_no].base_addr = addr2;
+ cy_card[card_no].ctl_addr.p9050 = addr0;
+ cy_card[card_no].irq = irq;
+ cy_card[card_no].bus_index = 1;
+ cy_card[card_no].first_line = cy_next_channel;
+ cy_card[card_no].nports = nchan;
+ retval = cy_init_card(&cy_card[card_no]);
+ if (retval)
+ goto err_null;
+
+ pci_set_drvdata(pdev, &cy_card[card_no]);
+
+ if (device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo ||
+ device_id == PCI_DEVICE_ID_CYCLOM_Y_Hi) {
+ /* enable interrupts in the PCI interface */
+ plx_ver = readb(addr2 + CyPLX_VER) & 0x0f;
+ switch (plx_ver) {
+ case PLX_9050:
+ cy_writeb(addr0 + 0x4c, 0x43);
+ break;
+
+ case PLX_9060:
+ case PLX_9080:
+ default: /* Old boards, use PLX_9060 */
+ {
+ struct RUNTIME_9060 __iomem *ctl_addr = addr0;
+ plx_init(pdev, irq, ctl_addr);
+ cy_writew(&ctl_addr->intr_ctrl_stat,
+ readw(&ctl_addr->intr_ctrl_stat) | 0x0900);
+ break;
+ }
+ }
+ }
+
+ dev_info(&pdev->dev, "%s/PCI #%d found: %d channels starting from "
+ "port %d.\n", card_name, card_no + 1, nchan, cy_next_channel);
+ for (i = cy_next_channel; i < cy_next_channel + nchan; i++)
+ tty_register_device(cy_serial_driver, i, &pdev->dev);
+ cy_next_channel += nchan;
+
+ return 0;
+err_null:
+ cy_card[card_no].base_addr = NULL;
+ free_irq(irq, &cy_card[card_no]);
+err_unmap:
+ iounmap(addr0);
+ if (addr2)
+ iounmap(addr2);
+err_reg:
+ pci_release_regions(pdev);
+err_dis:
+ pci_disable_device(pdev);
+err:
+ return retval;
+}
+
+static void __devexit cy_pci_remove(struct pci_dev *pdev)
+{
+ struct cyclades_card *cinfo = pci_get_drvdata(pdev);
+ unsigned int i;
+
+ /* non-Z with old PLX */
+ if (!cy_is_Z(cinfo) && (readb(cinfo->base_addr + CyPLX_VER) & 0x0f) ==
+ PLX_9050)
+ cy_writeb(cinfo->ctl_addr.p9050 + 0x4c, 0);
+ else
+#ifndef CONFIG_CYZ_INTR
+ if (!cy_is_Z(cinfo))
+#endif
+ cy_writew(&cinfo->ctl_addr.p9060->intr_ctrl_stat,
+ readw(&cinfo->ctl_addr.p9060->intr_ctrl_stat) &
+ ~0x0900);
+
+ iounmap(cinfo->base_addr);
+ if (cinfo->ctl_addr.p9050)
+ iounmap(cinfo->ctl_addr.p9050);
+ if (cinfo->irq
+#ifndef CONFIG_CYZ_INTR
+ && !cy_is_Z(cinfo)
+#endif /* CONFIG_CYZ_INTR */
+ )
+ free_irq(cinfo->irq, cinfo);
+ pci_release_regions(pdev);
+
+ cinfo->base_addr = NULL;
+ for (i = cinfo->first_line; i < cinfo->first_line +
+ cinfo->nports; i++)
+ tty_unregister_device(cy_serial_driver, i);
+ cinfo->nports = 0;
+ kfree(cinfo->ports);
+}
+
+static struct pci_driver cy_pci_driver = {
+ .name = "cyclades",
+ .id_table = cy_pci_dev_id,
+ .probe = cy_pci_probe,
+ .remove = __devexit_p(cy_pci_remove)
+};
+#endif
+
+static int cyclades_proc_show(struct seq_file *m, void *v)
+{
+ struct cyclades_port *info;
+ unsigned int i, j;
+ __u32 cur_jifs = jiffies;
+
+ seq_puts(m, "Dev TimeOpen BytesOut IdleOut BytesIn "
+ "IdleIn Overruns Ldisc\n");
+
+ /* Output one line for each known port */
+ for (i = 0; i < NR_CARDS; i++)
+ for (j = 0; j < cy_card[i].nports; j++) {
+ info = &cy_card[i].ports[j];
+
+ if (info->port.count) {
+ /* XXX is the ldisc num worth this? */
+ struct tty_struct *tty;
+ struct tty_ldisc *ld;
+ int num = 0;
+ tty = tty_port_tty_get(&info->port);
+ if (tty) {
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ num = ld->ops->num;
+ tty_ldisc_deref(ld);
+ }
+ tty_kref_put(tty);
+ }
+ seq_printf(m, "%3d %8lu %10lu %8lu "
+ "%10lu %8lu %9lu %6d\n", info->line,
+ (cur_jifs - info->idle_stats.in_use) /
+ HZ, info->idle_stats.xmit_bytes,
+ (cur_jifs - info->idle_stats.xmit_idle)/
+ HZ, info->idle_stats.recv_bytes,
+ (cur_jifs - info->idle_stats.recv_idle)/
+ HZ, info->idle_stats.overruns,
+ num);
+ } else
+ seq_printf(m, "%3d %8lu %10lu %8lu "
+ "%10lu %8lu %9lu %6ld\n",
+ info->line, 0L, 0L, 0L, 0L, 0L, 0L, 0L);
+ }
+ return 0;
+}
+
+static int cyclades_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, cyclades_proc_show, NULL);
+}
+
+static const struct file_operations cyclades_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = cyclades_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* 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 const 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,
+ .tiocmget = cy_tiocmget,
+ .tiocmset = cy_tiocmset,
+ .get_icount = cy_get_icount,
+ .proc_fops = &cyclades_proc_fops,
+};
+
+static int __init cy_init(void)
+{
+ unsigned int nboards;
+ int retval = -ENOMEM;
+
+ cy_serial_driver = alloc_tty_driver(NR_PORTS);
+ if (!cy_serial_driver)
+ goto err;
+
+ printk(KERN_INFO "Cyclades driver " CY_VERSION "\n");
+
+ /* 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->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_DRIVER_DYNAMIC_DEV;
+ tty_set_operations(cy_serial_driver, &cy_ops);
+
+ retval = tty_register_driver(cy_serial_driver);
+ if (retval) {
+ printk(KERN_ERR "Couldn't register Cyclades serial driver\n");
+ goto err_frtty;
+ }
+
+ /* 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 */
+ nboards = cy_detect_isa();
+
+#ifdef CONFIG_PCI
+ /* look for pci boards */
+ retval = pci_register_driver(&cy_pci_driver);
+ if (retval && !nboards) {
+ tty_unregister_driver(cy_serial_driver);
+ goto err_frtty;
+ }
+#endif
+
+ return 0;
+err_frtty:
+ put_tty_driver(cy_serial_driver);
+err:
+ return retval;
+} /* cy_init */
+
+static void __exit cy_cleanup_module(void)
+{
+ struct cyclades_card *card;
+ unsigned int i, e1;
+
+#ifndef CONFIG_CYZ_INTR
+ del_timer_sync(&cyz_timerlist);
+#endif /* CONFIG_CYZ_INTR */
+
+ e1 = tty_unregister_driver(cy_serial_driver);
+ if (e1)
+ printk(KERN_ERR "failed to unregister Cyclades serial "
+ "driver(%d)\n", e1);
+
+#ifdef CONFIG_PCI
+ pci_unregister_driver(&cy_pci_driver);
+#endif
+
+ for (i = 0; i < NR_CARDS; i++) {
+ card = &cy_card[i];
+ if (card->base_addr) {
+ /* clear interrupt */
+ cy_writeb(card->base_addr + Cy_ClrIntr, 0);
+ iounmap(card->base_addr);
+ if (card->ctl_addr.p9050)
+ iounmap(card->ctl_addr.p9050);
+ if (card->irq
+#ifndef CONFIG_CYZ_INTR
+ && !cy_is_Z(card)
+#endif /* CONFIG_CYZ_INTR */
+ )
+ free_irq(card->irq, card);
+ for (e1 = card->first_line; e1 < card->first_line +
+ card->nports; e1++)
+ tty_unregister_device(cy_serial_driver, e1);
+ kfree(card->ports);
+ }
+ }
+
+ put_tty_driver(cy_serial_driver);
+} /* cy_cleanup_module */
+
+module_init(cy_init);
+module_exit(cy_cleanup_module);
+
+MODULE_LICENSE("GPL");
+MODULE_VERSION(CY_VERSION);
+MODULE_ALIAS_CHARDEV_MAJOR(CYCLADES_MAJOR);
+MODULE_FIRMWARE("cyzfirm.bin");
diff --git a/drivers/tty/ehv_bytechan.c b/drivers/tty/ehv_bytechan.c
new file mode 100644
index 00000000000..1595dba0072
--- /dev/null
+++ b/drivers/tty/ehv_bytechan.c
@@ -0,0 +1,881 @@
+/* ePAPR hypervisor byte channel device driver
+ *
+ * Copyright 2009-2011 Freescale Semiconductor, Inc.
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * 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.
+ *
+ * This driver support three distinct interfaces, all of which are related to
+ * ePAPR hypervisor byte channels.
+ *
+ * 1) An early-console (udbg) driver. This provides early console output
+ * through a byte channel. The byte channel handle must be specified in a
+ * Kconfig option.
+ *
+ * 2) A normal console driver. Output is sent to the byte channel designated
+ * for stdout in the device tree. The console driver is for handling kernel
+ * printk calls.
+ *
+ * 3) A tty driver, which is used to handle user-space input and output. The
+ * byte channel used for the console is designated as the default tty.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <asm/epapr_hcalls.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/cdev.h>
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/circ_buf.h>
+#include <asm/udbg.h>
+
+/* The size of the transmit circular buffer. This must be a power of two. */
+#define BUF_SIZE 2048
+
+/* Per-byte channel private data */
+struct ehv_bc_data {
+ struct device *dev;
+ struct tty_port port;
+ uint32_t handle;
+ unsigned int rx_irq;
+ unsigned int tx_irq;
+
+ spinlock_t lock; /* lock for transmit buffer */
+ unsigned char buf[BUF_SIZE]; /* transmit circular buffer */
+ unsigned int head; /* circular buffer head */
+ unsigned int tail; /* circular buffer tail */
+
+ int tx_irq_enabled; /* true == TX interrupt is enabled */
+};
+
+/* Array of byte channel objects */
+static struct ehv_bc_data *bcs;
+
+/* Byte channel handle for stdout (and stdin), taken from device tree */
+static unsigned int stdout_bc;
+
+/* Virtual IRQ for the byte channel handle for stdin, taken from device tree */
+static unsigned int stdout_irq;
+
+/**************************** SUPPORT FUNCTIONS ****************************/
+
+/*
+ * Enable the transmit interrupt
+ *
+ * Unlike a serial device, byte channels have no mechanism for disabling their
+ * own receive or transmit interrupts. To emulate that feature, we toggle
+ * the IRQ in the kernel.
+ *
+ * We cannot just blindly call enable_irq() or disable_irq(), because these
+ * calls are reference counted. This means that we cannot call enable_irq()
+ * if interrupts are already enabled. This can happen in two situations:
+ *
+ * 1. The tty layer makes two back-to-back calls to ehv_bc_tty_write()
+ * 2. A transmit interrupt occurs while executing ehv_bc_tx_dequeue()
+ *
+ * To work around this, we keep a flag to tell us if the IRQ is enabled or not.
+ */
+static void enable_tx_interrupt(struct ehv_bc_data *bc)
+{
+ if (!bc->tx_irq_enabled) {
+ enable_irq(bc->tx_irq);
+ bc->tx_irq_enabled = 1;
+ }
+}
+
+static void disable_tx_interrupt(struct ehv_bc_data *bc)
+{
+ if (bc->tx_irq_enabled) {
+ disable_irq_nosync(bc->tx_irq);
+ bc->tx_irq_enabled = 0;
+ }
+}
+
+/*
+ * find the byte channel handle to use for the console
+ *
+ * The byte channel to be used for the console is specified via a "stdout"
+ * property in the /chosen node.
+ *
+ * For compatible with legacy device trees, we also look for a "stdout" alias.
+ */
+static int find_console_handle(void)
+{
+ struct device_node *np, *np2;
+ const char *sprop = NULL;
+ const uint32_t *iprop;
+
+ np = of_find_node_by_path("/chosen");
+ if (np)
+ sprop = of_get_property(np, "stdout-path", NULL);
+
+ if (!np || !sprop) {
+ of_node_put(np);
+ np = of_find_node_by_name(NULL, "aliases");
+ if (np)
+ sprop = of_get_property(np, "stdout", NULL);
+ }
+
+ if (!sprop) {
+ of_node_put(np);
+ return 0;
+ }
+
+ /* We don't care what the aliased node is actually called. We only
+ * care if it's compatible with "epapr,hv-byte-channel", because that
+ * indicates that it's a byte channel node. We use a temporary
+ * variable, 'np2', because we can't release 'np' until we're done with
+ * 'sprop'.
+ */
+ np2 = of_find_node_by_path(sprop);
+ of_node_put(np);
+ np = np2;
+ if (!np) {
+ pr_warning("ehv-bc: stdout node '%s' does not exist\n", sprop);
+ return 0;
+ }
+
+ /* Is it a byte channel? */
+ if (!of_device_is_compatible(np, "epapr,hv-byte-channel")) {
+ of_node_put(np);
+ return 0;
+ }
+
+ stdout_irq = irq_of_parse_and_map(np, 0);
+ if (stdout_irq == NO_IRQ) {
+ pr_err("ehv-bc: no 'interrupts' property in %s node\n", sprop);
+ of_node_put(np);
+ return 0;
+ }
+
+ /*
+ * The 'hv-handle' property contains the handle for this byte channel.
+ */
+ iprop = of_get_property(np, "hv-handle", NULL);
+ if (!iprop) {
+ pr_err("ehv-bc: no 'hv-handle' property in %s node\n",
+ np->name);
+ of_node_put(np);
+ return 0;
+ }
+ stdout_bc = be32_to_cpu(*iprop);
+
+ of_node_put(np);
+ return 1;
+}
+
+/*************************** EARLY CONSOLE DRIVER ***************************/
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_EHV_BC
+
+/*
+ * send a byte to a byte channel, wait if necessary
+ *
+ * This function sends a byte to a byte channel, and it waits and
+ * retries if the byte channel is full. It returns if the character
+ * has been sent, or if some error has occurred.
+ *
+ */
+static void byte_channel_spin_send(const char data)
+{
+ int ret, count;
+
+ do {
+ count = 1;
+ ret = ev_byte_channel_send(CONFIG_PPC_EARLY_DEBUG_EHV_BC_HANDLE,
+ &count, &data);
+ } while (ret == EV_EAGAIN);
+}
+
+/*
+ * The udbg subsystem calls this function to display a single character.
+ * We convert CR to a CR/LF.
+ */
+static void ehv_bc_udbg_putc(char c)
+{
+ if (c == '\n')
+ byte_channel_spin_send('\r');
+
+ byte_channel_spin_send(c);
+}
+
+/*
+ * early console initialization
+ *
+ * PowerPC kernels support an early printk console, also known as udbg.
+ * This function must be called via the ppc_md.init_early function pointer.
+ * At this point, the device tree has been unflattened, so we can obtain the
+ * byte channel handle for stdout.
+ *
+ * We only support displaying of characters (putc). We do not support
+ * keyboard input.
+ */
+void __init udbg_init_ehv_bc(void)
+{
+ unsigned int rx_count, tx_count;
+ unsigned int ret;
+
+ /* Verify the byte channel handle */
+ ret = ev_byte_channel_poll(CONFIG_PPC_EARLY_DEBUG_EHV_BC_HANDLE,
+ &rx_count, &tx_count);
+ if (ret)
+ return;
+
+ udbg_putc = ehv_bc_udbg_putc;
+ register_early_udbg_console();
+
+ udbg_printf("ehv-bc: early console using byte channel handle %u\n",
+ CONFIG_PPC_EARLY_DEBUG_EHV_BC_HANDLE);
+}
+
+#endif
+
+/****************************** CONSOLE DRIVER ******************************/
+
+static struct tty_driver *ehv_bc_driver;
+
+/*
+ * Byte channel console sending worker function.
+ *
+ * For consoles, if the output buffer is full, we should just spin until it
+ * clears.
+ */
+static int ehv_bc_console_byte_channel_send(unsigned int handle, const char *s,
+ unsigned int count)
+{
+ unsigned int len;
+ int ret = 0;
+
+ while (count) {
+ len = min_t(unsigned int, count, EV_BYTE_CHANNEL_MAX_BYTES);
+ do {
+ ret = ev_byte_channel_send(handle, &len, s);
+ } while (ret == EV_EAGAIN);
+ count -= len;
+ s += len;
+ }
+
+ return ret;
+}
+
+/*
+ * write a string to the console
+ *
+ * This function gets called to write a string from the kernel, typically from
+ * a printk(). This function spins until all data is written.
+ *
+ * We copy the data to a temporary buffer because we need to insert a \r in
+ * front of every \n. It's more efficient to copy the data to the buffer than
+ * it is to make multiple hcalls for each character or each newline.
+ */
+static void ehv_bc_console_write(struct console *co, const char *s,
+ unsigned int count)
+{
+ char s2[EV_BYTE_CHANNEL_MAX_BYTES];
+ unsigned int i, j = 0;
+ char c;
+
+ for (i = 0; i < count; i++) {
+ c = *s++;
+
+ if (c == '\n')
+ s2[j++] = '\r';
+
+ s2[j++] = c;
+ if (j >= (EV_BYTE_CHANNEL_MAX_BYTES - 1)) {
+ if (ehv_bc_console_byte_channel_send(stdout_bc, s2, j))
+ return;
+ j = 0;
+ }
+ }
+
+ if (j)
+ ehv_bc_console_byte_channel_send(stdout_bc, s2, j);
+}
+
+/*
+ * When /dev/console is opened, the kernel iterates the console list looking
+ * for one with ->device and then calls that method. On success, it expects
+ * the passed-in int* to contain the minor number to use.
+ */
+static struct tty_driver *ehv_bc_console_device(struct console *co, int *index)
+{
+ *index = co->index;
+
+ return ehv_bc_driver;
+}
+
+static struct console ehv_bc_console = {
+ .name = "ttyEHV",
+ .write = ehv_bc_console_write,
+ .device = ehv_bc_console_device,
+ .flags = CON_PRINTBUFFER | CON_ENABLED,
+};
+
+/*
+ * Console initialization
+ *
+ * This is the first function that is called after the device tree is
+ * available, so here is where we determine the byte channel handle and IRQ for
+ * stdout/stdin, even though that information is used by the tty and character
+ * drivers.
+ */
+static int __init ehv_bc_console_init(void)
+{
+ if (!find_console_handle()) {
+ pr_debug("ehv-bc: stdout is not a byte channel\n");
+ return -ENODEV;
+ }
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_EHV_BC
+ /* Print a friendly warning if the user chose the wrong byte channel
+ * handle for udbg.
+ */
+ if (stdout_bc != CONFIG_PPC_EARLY_DEBUG_EHV_BC_HANDLE)
+ pr_warning("ehv-bc: udbg handle %u is not the stdout handle\n",
+ CONFIG_PPC_EARLY_DEBUG_EHV_BC_HANDLE);
+#endif
+
+ /* add_preferred_console() must be called before register_console(),
+ otherwise it won't work. However, we don't want to enumerate all the
+ byte channels here, either, since we only care about one. */
+
+ add_preferred_console(ehv_bc_console.name, ehv_bc_console.index, NULL);
+ register_console(&ehv_bc_console);
+
+ pr_info("ehv-bc: registered console driver for byte channel %u\n",
+ stdout_bc);
+
+ return 0;
+}
+console_initcall(ehv_bc_console_init);
+
+/******************************** TTY DRIVER ********************************/
+
+/*
+ * byte channel receive interupt handler
+ *
+ * This ISR is called whenever data is available on a byte channel.
+ */
+static irqreturn_t ehv_bc_tty_rx_isr(int irq, void *data)
+{
+ struct ehv_bc_data *bc = data;
+ struct tty_struct *ttys = tty_port_tty_get(&bc->port);
+ unsigned int rx_count, tx_count, len;
+ int count;
+ char buffer[EV_BYTE_CHANNEL_MAX_BYTES];
+ int ret;
+
+ /* ttys could be NULL during a hangup */
+ if (!ttys)
+ return IRQ_HANDLED;
+
+ /* Find out how much data needs to be read, and then ask the TTY layer
+ * if it can handle that much. We want to ensure that every byte we
+ * read from the byte channel will be accepted by the TTY layer.
+ */
+ ev_byte_channel_poll(bc->handle, &rx_count, &tx_count);
+ count = tty_buffer_request_room(ttys, rx_count);
+
+ /* 'count' is the maximum amount of data the TTY layer can accept at
+ * this time. However, during testing, I was never able to get 'count'
+ * to be less than 'rx_count'. I'm not sure whether I'm calling it
+ * correctly.
+ */
+
+ while (count > 0) {
+ len = min_t(unsigned int, count, sizeof(buffer));
+
+ /* Read some data from the byte channel. This function will
+ * never return more than EV_BYTE_CHANNEL_MAX_BYTES bytes.
+ */
+ ev_byte_channel_receive(bc->handle, &len, buffer);
+
+ /* 'len' is now the amount of data that's been received. 'len'
+ * can't be zero, and most likely it's equal to one.
+ */
+
+ /* Pass the received data to the tty layer. */
+ ret = tty_insert_flip_string(ttys, buffer, len);
+
+ /* 'ret' is the number of bytes that the TTY layer accepted.
+ * If it's not equal to 'len', then it means the buffer is
+ * full, which should never happen. If it does happen, we can
+ * exit gracefully, but we drop the last 'len - ret' characters
+ * that we read from the byte channel.
+ */
+ if (ret != len)
+ break;
+
+ count -= len;
+ }
+
+ /* Tell the tty layer that we're done. */
+ tty_flip_buffer_push(ttys);
+
+ tty_kref_put(ttys);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * dequeue the transmit buffer to the hypervisor
+ *
+ * This function, which can be called in interrupt context, dequeues as much
+ * data as possible from the transmit buffer to the byte channel.
+ */
+static void ehv_bc_tx_dequeue(struct ehv_bc_data *bc)
+{
+ unsigned int count;
+ unsigned int len, ret;
+ unsigned long flags;
+
+ do {
+ spin_lock_irqsave(&bc->lock, flags);
+ len = min_t(unsigned int,
+ CIRC_CNT_TO_END(bc->head, bc->tail, BUF_SIZE),
+ EV_BYTE_CHANNEL_MAX_BYTES);
+
+ ret = ev_byte_channel_send(bc->handle, &len, bc->buf + bc->tail);
+
+ /* 'len' is valid only if the return code is 0 or EV_EAGAIN */
+ if (!ret || (ret == EV_EAGAIN))
+ bc->tail = (bc->tail + len) & (BUF_SIZE - 1);
+
+ count = CIRC_CNT(bc->head, bc->tail, BUF_SIZE);
+ spin_unlock_irqrestore(&bc->lock, flags);
+ } while (count && !ret);
+
+ spin_lock_irqsave(&bc->lock, flags);
+ if (CIRC_CNT(bc->head, bc->tail, BUF_SIZE))
+ /*
+ * If we haven't emptied the buffer, then enable the TX IRQ.
+ * We'll get an interrupt when there's more room in the
+ * hypervisor's output buffer.
+ */
+ enable_tx_interrupt(bc);
+ else
+ disable_tx_interrupt(bc);
+ spin_unlock_irqrestore(&bc->lock, flags);
+}
+
+/*
+ * byte channel transmit interupt handler
+ *
+ * This ISR is called whenever space becomes available for transmitting
+ * characters on a byte channel.
+ */
+static irqreturn_t ehv_bc_tty_tx_isr(int irq, void *data)
+{
+ struct ehv_bc_data *bc = data;
+ struct tty_struct *ttys = tty_port_tty_get(&bc->port);
+
+ ehv_bc_tx_dequeue(bc);
+ if (ttys) {
+ tty_wakeup(ttys);
+ tty_kref_put(ttys);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * This function is called when the tty layer has data for us send. We store
+ * the data first in a circular buffer, and then dequeue as much of that data
+ * as possible.
+ *
+ * We don't need to worry about whether there is enough room in the buffer for
+ * all the data. The purpose of ehv_bc_tty_write_room() is to tell the tty
+ * layer how much data it can safely send to us. We guarantee that
+ * ehv_bc_tty_write_room() will never lie, so the tty layer will never send us
+ * too much data.
+ */
+static int ehv_bc_tty_write(struct tty_struct *ttys, const unsigned char *s,
+ int count)
+{
+ struct ehv_bc_data *bc = ttys->driver_data;
+ unsigned long flags;
+ unsigned int len;
+ unsigned int written = 0;
+
+ while (1) {
+ spin_lock_irqsave(&bc->lock, flags);
+ len = CIRC_SPACE_TO_END(bc->head, bc->tail, BUF_SIZE);
+ if (count < len)
+ len = count;
+ if (len) {
+ memcpy(bc->buf + bc->head, s, len);
+ bc->head = (bc->head + len) & (BUF_SIZE - 1);
+ }
+ spin_unlock_irqrestore(&bc->lock, flags);
+ if (!len)
+ break;
+
+ s += len;
+ count -= len;
+ written += len;
+ }
+
+ ehv_bc_tx_dequeue(bc);
+
+ return written;
+}
+
+/*
+ * This function can be called multiple times for a given tty_struct, which is
+ * why we initialize bc->ttys in ehv_bc_tty_port_activate() instead.
+ *
+ * The tty layer will still call this function even if the device was not
+ * registered (i.e. tty_register_device() was not called). This happens
+ * because tty_register_device() is optional and some legacy drivers don't
+ * use it. So we need to check for that.
+ */
+static int ehv_bc_tty_open(struct tty_struct *ttys, struct file *filp)
+{
+ struct ehv_bc_data *bc = &bcs[ttys->index];
+
+ if (!bc->dev)
+ return -ENODEV;
+
+ return tty_port_open(&bc->port, ttys, filp);
+}
+
+/*
+ * Amazingly, if ehv_bc_tty_open() returns an error code, the tty layer will
+ * still call this function to close the tty device. So we can't assume that
+ * the tty port has been initialized.
+ */
+static void ehv_bc_tty_close(struct tty_struct *ttys, struct file *filp)
+{
+ struct ehv_bc_data *bc = &bcs[ttys->index];
+
+ if (bc->dev)
+ tty_port_close(&bc->port, ttys, filp);
+}
+
+/*
+ * Return the amount of space in the output buffer
+ *
+ * This is actually a contract between the driver and the tty layer outlining
+ * how much write room the driver can guarantee will be sent OR BUFFERED. This
+ * driver MUST honor the return value.
+ */
+static int ehv_bc_tty_write_room(struct tty_struct *ttys)
+{
+ struct ehv_bc_data *bc = ttys->driver_data;
+ unsigned long flags;
+ int count;
+
+ spin_lock_irqsave(&bc->lock, flags);
+ count = CIRC_SPACE(bc->head, bc->tail, BUF_SIZE);
+ spin_unlock_irqrestore(&bc->lock, flags);
+
+ return count;
+}
+
+/*
+ * Stop sending data to the tty layer
+ *
+ * This function is called when the tty layer's input buffers are getting full,
+ * so the driver should stop sending it data. The easiest way to do this is to
+ * disable the RX IRQ, which will prevent ehv_bc_tty_rx_isr() from being
+ * called.
+ *
+ * The hypervisor will continue to queue up any incoming data. If there is any
+ * data in the queue when the RX interrupt is enabled, we'll immediately get an
+ * RX interrupt.
+ */
+static void ehv_bc_tty_throttle(struct tty_struct *ttys)
+{
+ struct ehv_bc_data *bc = ttys->driver_data;
+
+ disable_irq(bc->rx_irq);
+}
+
+/*
+ * Resume sending data to the tty layer
+ *
+ * This function is called after previously calling ehv_bc_tty_throttle(). The
+ * tty layer's input buffers now have more room, so the driver can resume
+ * sending it data.
+ */
+static void ehv_bc_tty_unthrottle(struct tty_struct *ttys)
+{
+ struct ehv_bc_data *bc = ttys->driver_data;
+
+ /* If there is any data in the queue when the RX interrupt is enabled,
+ * we'll immediately get an RX interrupt.
+ */
+ enable_irq(bc->rx_irq);
+}
+
+static void ehv_bc_tty_hangup(struct tty_struct *ttys)
+{
+ struct ehv_bc_data *bc = ttys->driver_data;
+
+ ehv_bc_tx_dequeue(bc);
+ tty_port_hangup(&bc->port);
+}
+
+/*
+ * TTY driver operations
+ *
+ * If we could ask the hypervisor how much data is still in the TX buffer, or
+ * at least how big the TX buffers are, then we could implement the
+ * .wait_until_sent and .chars_in_buffer functions.
+ */
+static const struct tty_operations ehv_bc_ops = {
+ .open = ehv_bc_tty_open,
+ .close = ehv_bc_tty_close,
+ .write = ehv_bc_tty_write,
+ .write_room = ehv_bc_tty_write_room,
+ .throttle = ehv_bc_tty_throttle,
+ .unthrottle = ehv_bc_tty_unthrottle,
+ .hangup = ehv_bc_tty_hangup,
+};
+
+/*
+ * initialize the TTY port
+ *
+ * This function will only be called once, no matter how many times
+ * ehv_bc_tty_open() is called. That's why we register the ISR here, and also
+ * why we initialize tty_struct-related variables here.
+ */
+static int ehv_bc_tty_port_activate(struct tty_port *port,
+ struct tty_struct *ttys)
+{
+ struct ehv_bc_data *bc = container_of(port, struct ehv_bc_data, port);
+ int ret;
+
+ ttys->driver_data = bc;
+
+ ret = request_irq(bc->rx_irq, ehv_bc_tty_rx_isr, 0, "ehv-bc", bc);
+ if (ret < 0) {
+ dev_err(bc->dev, "could not request rx irq %u (ret=%i)\n",
+ bc->rx_irq, ret);
+ return ret;
+ }
+
+ /* request_irq also enables the IRQ */
+ bc->tx_irq_enabled = 1;
+
+ ret = request_irq(bc->tx_irq, ehv_bc_tty_tx_isr, 0, "ehv-bc", bc);
+ if (ret < 0) {
+ dev_err(bc->dev, "could not request tx irq %u (ret=%i)\n",
+ bc->tx_irq, ret);
+ free_irq(bc->rx_irq, bc);
+ return ret;
+ }
+
+ /* The TX IRQ is enabled only when we can't write all the data to the
+ * byte channel at once, so by default it's disabled.
+ */
+ disable_tx_interrupt(bc);
+
+ return 0;
+}
+
+static void ehv_bc_tty_port_shutdown(struct tty_port *port)
+{
+ struct ehv_bc_data *bc = container_of(port, struct ehv_bc_data, port);
+
+ free_irq(bc->tx_irq, bc);
+ free_irq(bc->rx_irq, bc);
+}
+
+static const struct tty_port_operations ehv_bc_tty_port_ops = {
+ .activate = ehv_bc_tty_port_activate,
+ .shutdown = ehv_bc_tty_port_shutdown,
+};
+
+static int __devinit ehv_bc_tty_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct ehv_bc_data *bc;
+ const uint32_t *iprop;
+ unsigned int handle;
+ int ret;
+ static unsigned int index = 1;
+ unsigned int i;
+
+ iprop = of_get_property(np, "hv-handle", NULL);
+ if (!iprop) {
+ dev_err(&pdev->dev, "no 'hv-handle' property in %s node\n",
+ np->name);
+ return -ENODEV;
+ }
+
+ /* We already told the console layer that the index for the console
+ * device is zero, so we need to make sure that we use that index when
+ * we probe the console byte channel node.
+ */
+ handle = be32_to_cpu(*iprop);
+ i = (handle == stdout_bc) ? 0 : index++;
+ bc = &bcs[i];
+
+ bc->handle = handle;
+ bc->head = 0;
+ bc->tail = 0;
+ spin_lock_init(&bc->lock);
+
+ bc->rx_irq = irq_of_parse_and_map(np, 0);
+ bc->tx_irq = irq_of_parse_and_map(np, 1);
+ if ((bc->rx_irq == NO_IRQ) || (bc->tx_irq == NO_IRQ)) {
+ dev_err(&pdev->dev, "no 'interrupts' property in %s node\n",
+ np->name);
+ ret = -ENODEV;
+ goto error;
+ }
+
+ bc->dev = tty_register_device(ehv_bc_driver, i, &pdev->dev);
+ if (IS_ERR(bc->dev)) {
+ ret = PTR_ERR(bc->dev);
+ dev_err(&pdev->dev, "could not register tty (ret=%i)\n", ret);
+ goto error;
+ }
+
+ tty_port_init(&bc->port);
+ bc->port.ops = &ehv_bc_tty_port_ops;
+
+ dev_set_drvdata(&pdev->dev, bc);
+
+ dev_info(&pdev->dev, "registered /dev/%s%u for byte channel %u\n",
+ ehv_bc_driver->name, i, bc->handle);
+
+ return 0;
+
+error:
+ irq_dispose_mapping(bc->tx_irq);
+ irq_dispose_mapping(bc->rx_irq);
+
+ memset(bc, 0, sizeof(struct ehv_bc_data));
+ return ret;
+}
+
+static int ehv_bc_tty_remove(struct platform_device *pdev)
+{
+ struct ehv_bc_data *bc = dev_get_drvdata(&pdev->dev);
+
+ tty_unregister_device(ehv_bc_driver, bc - bcs);
+
+ irq_dispose_mapping(bc->tx_irq);
+ irq_dispose_mapping(bc->rx_irq);
+
+ return 0;
+}
+
+static const struct of_device_id ehv_bc_tty_of_ids[] = {
+ { .compatible = "epapr,hv-byte-channel" },
+ {}
+};
+
+static struct platform_driver ehv_bc_tty_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ehv-bc",
+ .of_match_table = ehv_bc_tty_of_ids,
+ },
+ .probe = ehv_bc_tty_probe,
+ .remove = ehv_bc_tty_remove,
+};
+
+/**
+ * ehv_bc_init - ePAPR hypervisor byte channel driver initialization
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init ehv_bc_init(void)
+{
+ struct device_node *np;
+ unsigned int count = 0; /* Number of elements in bcs[] */
+ int ret;
+
+ pr_info("ePAPR hypervisor byte channel driver\n");
+
+ /* Count the number of byte channels */
+ for_each_compatible_node(np, NULL, "epapr,hv-byte-channel")
+ count++;
+
+ if (!count)
+ return -ENODEV;
+
+ /* The array index of an element in bcs[] is the same as the tty index
+ * for that element. If you know the address of an element in the
+ * array, then you can use pointer math (e.g. "bc - bcs") to get its
+ * tty index.
+ */
+ bcs = kzalloc(count * sizeof(struct ehv_bc_data), GFP_KERNEL);
+ if (!bcs)
+ return -ENOMEM;
+
+ ehv_bc_driver = alloc_tty_driver(count);
+ if (!ehv_bc_driver) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ehv_bc_driver->owner = THIS_MODULE;
+ ehv_bc_driver->driver_name = "ehv-bc";
+ ehv_bc_driver->name = ehv_bc_console.name;
+ ehv_bc_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ ehv_bc_driver->subtype = SYSTEM_TYPE_CONSOLE;
+ ehv_bc_driver->init_termios = tty_std_termios;
+ ehv_bc_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+ tty_set_operations(ehv_bc_driver, &ehv_bc_ops);
+
+ ret = tty_register_driver(ehv_bc_driver);
+ if (ret) {
+ pr_err("ehv-bc: could not register tty driver (ret=%i)\n", ret);
+ goto error;
+ }
+
+ ret = platform_driver_register(&ehv_bc_tty_driver);
+ if (ret) {
+ pr_err("ehv-bc: could not register platform driver (ret=%i)\n",
+ ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ if (ehv_bc_driver) {
+ tty_unregister_driver(ehv_bc_driver);
+ put_tty_driver(ehv_bc_driver);
+ }
+
+ kfree(bcs);
+
+ return ret;
+}
+
+
+/**
+ * ehv_bc_exit - ePAPR hypervisor byte channel driver termination
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit ehv_bc_exit(void)
+{
+ tty_unregister_driver(ehv_bc_driver);
+ put_tty_driver(ehv_bc_driver);
+ kfree(bcs);
+}
+
+module_init(ehv_bc_init);
+module_exit(ehv_bc_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("ePAPR hypervisor byte channel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig
new file mode 100644
index 00000000000..4222035acfb
--- /dev/null
+++ b/drivers/tty/hvc/Kconfig
@@ -0,0 +1,119 @@
+config HVC_DRIVER
+ bool
+ help
+ Generic "hypervisor virtual console" infrastructure for various
+ hypervisors (pSeries, iSeries, Xen, lguest).
+ It will automatically be selected if one of the back-end console drivers
+ is selected.
+
+config HVC_IRQ
+ bool
+
+config HVC_CONSOLE
+ bool "pSeries Hypervisor Virtual Console support"
+ depends on PPC_PSERIES
+ select HVC_DRIVER
+ select HVC_IRQ
+ 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 HVC_OLD_HVSI
+ bool "Old driver for pSeries serial port (/dev/hvsi*)"
+ depends on HVC_CONSOLE
+ default n
+
+config HVC_ISERIES
+ bool "iSeries Hypervisor Virtual Console support"
+ depends on PPC_ISERIES
+ default y
+ select HVC_DRIVER
+ select HVC_IRQ
+ select VIOPATH
+ help
+ iSeries machines support a hypervisor virtual console.
+
+config HVC_OPAL
+ bool "OPAL Console support"
+ depends on PPC_POWERNV
+ select HVC_DRIVER
+ select HVC_IRQ
+ default y
+ help
+ PowerNV machines running under OPAL need that driver to get a console
+
+config HVC_RTAS
+ bool "IBM RTAS Console support"
+ depends on PPC_RTAS
+ select HVC_DRIVER
+ help
+ IBM Console device driver which makes use of RTAS
+
+config HVC_BEAT
+ bool "Toshiba's Beat Hypervisor Console support"
+ depends on PPC_CELLEB
+ select HVC_DRIVER
+ help
+ Toshiba's Cell Reference Set Beat Console device driver
+
+config HVC_IUCV
+ bool "z/VM IUCV Hypervisor console support (VM only)"
+ depends on S390
+ select HVC_DRIVER
+ select IUCV
+ default y
+ help
+ This driver provides a Hypervisor console (HVC) back-end to access
+ a Linux (console) terminal via a z/VM IUCV communication path.
+
+config HVC_XEN
+ bool "Xen Hypervisor Console support"
+ depends on XEN
+ select HVC_DRIVER
+ select HVC_IRQ
+ default y
+ help
+ Xen virtual console device driver
+
+config HVC_UDBG
+ bool "udbg based fake hypervisor console"
+ depends on PPC && EXPERIMENTAL
+ select HVC_DRIVER
+ default n
+
+config HVC_DCC
+ bool "ARM JTAG DCC console"
+ depends on ARM
+ select HVC_DRIVER
+ help
+ This console uses the JTAG DCC on ARM to create a console under the HVC
+ driver. This console is used through a JTAG only on ARM. If you don't have
+ a JTAG then you probably don't want this option.
+
+config HVC_BFIN_JTAG
+ bool "Blackfin JTAG console"
+ depends on BLACKFIN
+ select HVC_DRIVER
+ help
+ This console uses the Blackfin JTAG to create a console under the
+ the HVC driver. If you don't have JTAG, then you probably don't
+ want this option.
+
+config HVCS
+ tristate "IBM Hypervisor Virtual Console Server support"
+ depends on PPC_PSERIES && HVC_CONSOLE
+ 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. 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.
+
diff --git a/drivers/tty/hvc/Makefile b/drivers/tty/hvc/Makefile
new file mode 100644
index 00000000000..89abf40bc73
--- /dev/null
+++ b/drivers/tty/hvc/Makefile
@@ -0,0 +1,15 @@
+obj-$(CONFIG_HVC_CONSOLE) += hvc_vio.o hvsi_lib.o
+obj-$(CONFIG_HVC_OPAL) += hvc_opal.o hvsi_lib.o
+obj-$(CONFIG_HVC_OLD_HVSI) += hvsi.o
+obj-$(CONFIG_HVC_ISERIES) += hvc_iseries.o
+obj-$(CONFIG_HVC_RTAS) += hvc_rtas.o
+obj-$(CONFIG_HVC_TILE) += hvc_tile.o
+obj-$(CONFIG_HVC_DCC) += hvc_dcc.o
+obj-$(CONFIG_HVC_BEAT) += hvc_beat.o
+obj-$(CONFIG_HVC_DRIVER) += hvc_console.o
+obj-$(CONFIG_HVC_IRQ) += hvc_irq.o
+obj-$(CONFIG_HVC_XEN) += hvc_xen.o
+obj-$(CONFIG_HVC_IUCV) += hvc_iucv.o
+obj-$(CONFIG_HVC_UDBG) += hvc_udbg.o
+obj-$(CONFIG_HVC_BFIN_JTAG) += hvc_bfin_jtag.o
+obj-$(CONFIG_HVCS) += hvcs.o
diff --git a/drivers/tty/hvc/hvc_beat.c b/drivers/tty/hvc/hvc_beat.c
new file mode 100644
index 00000000000..5fe4631e2a6
--- /dev/null
+++ b/drivers/tty/hvc/hvc_beat.c
@@ -0,0 +1,134 @@
+/*
+ * Beat hypervisor console driver
+ *
+ * (C) Copyright 2006 TOSHIBA CORPORATION
+ *
+ * This code is based on drivers/char/hvc_rtas.c:
+ * (C) Copyright IBM Corporation 2001-2005
+ * (C) Copyright Red Hat, Inc. 2005
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/console.h>
+#include <asm/prom.h>
+#include <asm/hvconsole.h>
+#include <asm/firmware.h>
+
+#include "hvc_console.h"
+
+extern int64_t beat_get_term_char(uint64_t, uint64_t *, uint64_t *, uint64_t *);
+extern int64_t beat_put_term_char(uint64_t, uint64_t, uint64_t, uint64_t);
+
+struct hvc_struct *hvc_beat_dev = NULL;
+
+/* bug: only one queue is available regardless of vtermno */
+static int hvc_beat_get_chars(uint32_t vtermno, char *buf, int cnt)
+{
+ static unsigned char q[sizeof(unsigned long) * 2]
+ __attribute__((aligned(sizeof(unsigned long))));
+ static int qlen = 0;
+ u64 got;
+
+again:
+ if (qlen) {
+ if (qlen > cnt) {
+ memcpy(buf, q, cnt);
+ qlen -= cnt;
+ memmove(q + cnt, q, qlen);
+ return cnt;
+ } else { /* qlen <= cnt */
+ int r;
+
+ memcpy(buf, q, qlen);
+ r = qlen;
+ qlen = 0;
+ return r;
+ }
+ }
+ if (beat_get_term_char(vtermno, &got,
+ ((u64 *)q), ((u64 *)q) + 1) == 0) {
+ qlen = got;
+ goto again;
+ }
+ return 0;
+}
+
+static int hvc_beat_put_chars(uint32_t vtermno, const char *buf, int cnt)
+{
+ unsigned long kb[2];
+ int rest, nlen;
+
+ for (rest = cnt; rest > 0; rest -= nlen) {
+ nlen = (rest > 16) ? 16 : rest;
+ memcpy(kb, buf, nlen);
+ beat_put_term_char(vtermno, nlen, kb[0], kb[1]);
+ buf += nlen;
+ }
+ return cnt;
+}
+
+static const struct hv_ops hvc_beat_get_put_ops = {
+ .get_chars = hvc_beat_get_chars,
+ .put_chars = hvc_beat_put_chars,
+};
+
+static int hvc_beat_useit = 1;
+
+static int hvc_beat_config(char *p)
+{
+ hvc_beat_useit = simple_strtoul(p, NULL, 0);
+ return 0;
+}
+
+static int __init hvc_beat_console_init(void)
+{
+ if (hvc_beat_useit && of_machine_is_compatible("Beat")) {
+ hvc_instantiate(0, 0, &hvc_beat_get_put_ops);
+ }
+ return 0;
+}
+
+/* temp */
+static int __init hvc_beat_init(void)
+{
+ struct hvc_struct *hp;
+
+ if (!firmware_has_feature(FW_FEATURE_BEAT))
+ return -ENODEV;
+
+ hp = hvc_alloc(0, NO_IRQ, &hvc_beat_get_put_ops, 16);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+ hvc_beat_dev = hp;
+ return 0;
+}
+
+static void __exit hvc_beat_exit(void)
+{
+ if (hvc_beat_dev)
+ hvc_remove(hvc_beat_dev);
+}
+
+module_init(hvc_beat_init);
+module_exit(hvc_beat_exit);
+
+__setup("hvc_beat=", hvc_beat_config);
+
+console_initcall(hvc_beat_console_init);
diff --git a/drivers/tty/hvc/hvc_bfin_jtag.c b/drivers/tty/hvc/hvc_bfin_jtag.c
new file mode 100644
index 00000000000..31d6cc6a77a
--- /dev/null
+++ b/drivers/tty/hvc/hvc_bfin_jtag.c
@@ -0,0 +1,105 @@
+/*
+ * Console via Blackfin JTAG Communication
+ *
+ * Copyright 2008-2011 Analog Devices Inc.
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+
+#include "hvc_console.h"
+
+/* See the Debug/Emulation chapter in the HRM */
+#define EMUDOF 0x00000001 /* EMUDAT_OUT full & valid */
+#define EMUDIF 0x00000002 /* EMUDAT_IN full & valid */
+#define EMUDOOVF 0x00000004 /* EMUDAT_OUT overflow */
+#define EMUDIOVF 0x00000008 /* EMUDAT_IN overflow */
+
+/* Helper functions to glue the register API to simple C operations */
+static inline uint32_t bfin_write_emudat(uint32_t emudat)
+{
+ __asm__ __volatile__("emudat = %0;" : : "d"(emudat));
+ return emudat;
+}
+
+static inline uint32_t bfin_read_emudat(void)
+{
+ uint32_t emudat;
+ __asm__ __volatile__("%0 = emudat;" : "=d"(emudat));
+ return emudat;
+}
+
+/* Send data to the host */
+static int hvc_bfin_put_chars(uint32_t vt, const char *buf, int count)
+{
+ static uint32_t outbound_len;
+ uint32_t emudat;
+ int ret;
+
+ if (bfin_read_DBGSTAT() & EMUDOF)
+ return 0;
+
+ if (!outbound_len) {
+ outbound_len = count;
+ bfin_write_emudat(outbound_len);
+ return 0;
+ }
+
+ ret = min(outbound_len, (uint32_t)4);
+ memcpy(&emudat, buf, ret);
+ bfin_write_emudat(emudat);
+ outbound_len -= ret;
+
+ return ret;
+}
+
+/* Receive data from the host */
+static int hvc_bfin_get_chars(uint32_t vt, char *buf, int count)
+{
+ static uint32_t inbound_len;
+ uint32_t emudat;
+ int ret;
+
+ if (!(bfin_read_DBGSTAT() & EMUDIF))
+ return 0;
+ emudat = bfin_read_emudat();
+
+ if (!inbound_len) {
+ inbound_len = emudat;
+ return 0;
+ }
+
+ ret = min(inbound_len, (uint32_t)4);
+ memcpy(buf, &emudat, ret);
+ inbound_len -= ret;
+
+ return ret;
+}
+
+/* Glue the HVC layers to the Blackfin layers */
+static const struct hv_ops hvc_bfin_get_put_ops = {
+ .get_chars = hvc_bfin_get_chars,
+ .put_chars = hvc_bfin_put_chars,
+};
+
+static int __init hvc_bfin_console_init(void)
+{
+ hvc_instantiate(0, 0, &hvc_bfin_get_put_ops);
+ return 0;
+}
+console_initcall(hvc_bfin_console_init);
+
+static int __init hvc_bfin_init(void)
+{
+ hvc_alloc(0, 0, &hvc_bfin_get_put_ops, 128);
+ return 0;
+}
+device_initcall(hvc_bfin_init);
diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c
new file mode 100644
index 00000000000..b6b2d18fa38
--- /dev/null
+++ b/drivers/tty/hvc/hvc_console.c
@@ -0,0 +1,976 @@
+/*
+ * 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/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 <linux/freezer.h>
+#include <linux/slab.h>
+#include <linux/serial_core.h>
+
+#include <asm/uaccess.h>
+
+#include "hvc_console.h"
+
+#define HVC_MAJOR 229
+#define HVC_MINOR 0
+
+/*
+ * 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 */
+
+/*
+ * These sizes are most efficient for vio, because they are the
+ * native transfer size. We could make them selectable in the
+ * future to better deal with backends that want other buffer sizes.
+ */
+#define N_OUTBUF 16
+#define N_INBUF 16
+
+#define __ALIGNED__ __attribute__((__aligned__(sizeof(long))))
+
+static struct tty_driver *hvc_driver;
+static struct task_struct *hvc_task;
+
+/* Picks up late kicks after list walk but before schedule() */
+static int hvc_kicked;
+
+static int hvc_init(void);
+
+#ifdef CONFIG_MAGIC_SYSRQ
+static int sysrq_pressed;
+#endif
+
+/* dynamic list of hvc_struct instances */
+static LIST_HEAD(hvc_structs);
+
+/*
+ * Protect the list of hvc_struct instances from inserts and removals during
+ * list traversal.
+ */
+static DEFINE_SPINLOCK(hvc_structs_lock);
+
+/*
+ * This value is used to assign a tty->index value to a hvc_struct based
+ * upon order of exposure via hvc_probe(), when we can not match it to
+ * a console candidate registered with hvc_instantiate().
+ */
+static int last_hvc = -1;
+
+/*
+ * Do not call this function with either the hvc_structs_lock or the hvc_struct
+ * lock held. If successful, this function increments the kref reference
+ * count against the target hvc_struct so it should be released when finished.
+ */
+static 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) {
+ kref_get(&hp->kref);
+ 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;
+}
+
+
+/*
+ * 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 const struct hv_ops *cons_ops[MAX_NR_HVC_CONSOLES];
+static uint32_t vtermnos[MAX_NR_HVC_CONSOLES] =
+ {[0 ... MAX_NR_HVC_CONSOLES - 1] = -1};
+
+/*
+ * Console APIs, NOT TTY. These APIs are available immediately when
+ * hvc_console_setup() finds adapters.
+ */
+
+static void hvc_console_print(struct console *co, const char *b,
+ unsigned count)
+{
+ char c[N_OUTBUF] __ALIGNED__;
+ unsigned i = 0, n = 0;
+ int r, donecr = 0, index = co->index;
+
+ /* Console access attempt outside of acceptable console range. */
+ if (index >= MAX_NR_HVC_CONSOLES)
+ return;
+
+ /* This console adapter was removed so it is not usable. */
+ if (vtermnos[index] == -1)
+ 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 = cons_ops[index]->put_chars(vtermnos[index], c, i);
+ if (r <= 0) {
+ /* throw away characters on error
+ * but spin in case of -EAGAIN */
+ if (r != -EAGAIN)
+ 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)
+{
+ if (vtermnos[c->index] == -1)
+ return NULL;
+
+ *index = c->index;
+ return hvc_driver;
+}
+
+static int __init hvc_console_setup(struct console *co, char *options)
+{
+ if (co->index < 0 || co->index >= MAX_NR_HVC_CONSOLES)
+ return -ENODEV;
+
+ if (vtermnos[co->index] == -1)
+ return -ENODEV;
+
+ return 0;
+}
+
+static struct console hvc_console = {
+ .name = "hvc",
+ .write = hvc_console_print,
+ .device = hvc_console_device,
+ .setup = hvc_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+/*
+ * Early console initialization. Precedes driver initialization.
+ *
+ * (1) we are first, and the user specified another driver
+ * -- index will remain -1
+ * (2) we are first and the user specified no driver
+ * -- index will be set to 0, then we will fail setup.
+ * (3) we are first and the user specified our driver
+ * -- index will be set to user specified driver, and we will fail
+ * (4) we are after driver, and this initcall will register us
+ * -- if the user didn't specify a driver then the console will match
+ *
+ * Note that for cases 2 and 3, we will match later when the io driver
+ * calls hvc_instantiate() and call register again.
+ */
+static int __init hvc_console_init(void)
+{
+ register_console(&hvc_console);
+ return 0;
+}
+console_initcall(hvc_console_init);
+
+/* callback when the kboject ref count reaches zero. */
+static void destroy_hvc_struct(struct kref *kref)
+{
+ struct hvc_struct *hp = container_of(kref, struct hvc_struct, kref);
+ 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);
+}
+
+/*
+ * 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 they
+ * appear after early console init.
+ */
+int hvc_instantiate(uint32_t vtermno, int index, const struct hv_ops *ops)
+{
+ struct hvc_struct *hp;
+
+ if (index < 0 || index >= MAX_NR_HVC_CONSOLES)
+ return -1;
+
+ if (vtermnos[index] != -1)
+ return -1;
+
+ /* make sure no no tty has been registered in this index */
+ hp = hvc_get_by_index(index);
+ if (hp) {
+ kref_put(&hp->kref, destroy_hvc_struct);
+ return -1;
+ }
+
+ vtermnos[index] = vtermno;
+ cons_ops[index] = ops;
+
+ /* reserve all indices up to and including this index */
+ if (last_hvc < index)
+ last_hvc = index;
+
+ /* if this index is what the user requested, then register
+ * now (setup won't fail at this point). It's ok to just
+ * call register again if previously .setup failed.
+ */
+ if (index == hvc_console.index)
+ register_console(&hvc_console);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hvc_instantiate);
+
+/* Wake the sleeping khvcd */
+void hvc_kick(void)
+{
+ hvc_kicked = 1;
+ wake_up_process(hvc_task);
+}
+EXPORT_SYMBOL_GPL(hvc_kick);
+
+static void hvc_unthrottle(struct tty_struct *tty)
+{
+ hvc_kick();
+}
+
+/*
+ * 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 rc = 0;
+
+ /* Auto increments kref reference if found. */
+ if (!(hp = hvc_get_by_index(tty->index)))
+ return -ENODEV;
+
+ spin_lock_irqsave(&hp->lock, flags);
+ /* Check and then increment for fast path open. */
+ if (hp->count++ > 0) {
+ tty_kref_get(tty);
+ spin_unlock_irqrestore(&hp->lock, flags);
+ hvc_kick();
+ return 0;
+ } /* else count == 0 */
+
+ tty->driver_data = hp;
+
+ hp->tty = tty_kref_get(tty);
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (hp->ops->notifier_add)
+ rc = hp->ops->notifier_add(hp, hp->data);
+
+ /*
+ * If the notifier fails 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 kref reference.
+ */
+ if (rc) {
+ spin_lock_irqsave(&hp->lock, flags);
+ hp->tty = NULL;
+ spin_unlock_irqrestore(&hp->lock, flags);
+ tty_kref_put(tty);
+ tty->driver_data = NULL;
+ kref_put(&hp->kref, destroy_hvc_struct);
+ 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;
+ 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 kref reference wasn't made.
+ */
+ if (!tty->driver_data)
+ return;
+
+ hp = tty->driver_data;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ if (--hp->count == 0) {
+ /* We are done with the tty pointer now. */
+ hp->tty = NULL;
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (hp->ops->notifier_del)
+ hp->ops->notifier_del(hp, hp->data);
+
+ /* cancel pending tty resize work */
+ cancel_work_sync(&hp->tty_resize);
+
+ /*
+ * 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_from_close(tty, HVC_CLOSE_WAIT);
+ } 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);
+ }
+
+ tty_kref_put(tty);
+ kref_put(&hp->kref, destroy_hvc_struct);
+}
+
+static void hvc_hangup(struct tty_struct *tty)
+{
+ struct hvc_struct *hp = tty->driver_data;
+ unsigned long flags;
+ int temp_open_count;
+
+ if (!hp)
+ return;
+
+ /* cancel pending tty resize work */
+ cancel_work_sync(&hp->tty_resize);
+
+ 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;
+ }
+
+ temp_open_count = hp->count;
+ hp->count = 0;
+ hp->n_outbuf = 0;
+ hp->tty = NULL;
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (hp->ops->notifier_hangup)
+ hp->ops->notifier_hangup(hp, hp->data);
+
+ while(temp_open_count) {
+ --temp_open_count;
+ tty_kref_put(tty);
+ kref_put(&hp->kref, destroy_hvc_struct);
+ }
+}
+
+/*
+ * Push buffered characters whether they were just recently buffered or waiting
+ * on a blocked hypervisor. Call this function with hp->lock held.
+ */
+static int hvc_push(struct hvc_struct *hp)
+{
+ int n;
+
+ n = hp->ops->put_chars(hp->vtermno, hp->outbuf, hp->n_outbuf);
+ if (n <= 0) {
+ if (n == 0 || n == -EAGAIN) {
+ hp->do_wakeup = 1;
+ return 0;
+ }
+ /* 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;
+
+ return n;
+}
+
+static int hvc_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ struct hvc_struct *hp = tty->driver_data;
+ unsigned long flags;
+ int rsize, written = 0;
+
+ /* This write was probably executed during a tty close. */
+ if (!hp)
+ return -EPIPE;
+
+ if (hp->count <= 0)
+ return -EIO;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ /* Push pending writes */
+ if (hp->n_outbuf > 0)
+ hvc_push(hp);
+
+ while (count > 0 && (rsize = hp->outbuf_size - 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);
+
+ /*
+ * Racy, but harmless, kick thread if there is still pending data.
+ */
+ if (hp->n_outbuf)
+ hvc_kick();
+
+ return written;
+}
+
+/**
+ * hvc_set_winsz() - Resize the hvc tty terminal window.
+ * @work: work structure.
+ *
+ * The routine shall not be called within an atomic context because it
+ * might sleep.
+ *
+ * Locking: hp->lock
+ */
+static void hvc_set_winsz(struct work_struct *work)
+{
+ struct hvc_struct *hp;
+ unsigned long hvc_flags;
+ struct tty_struct *tty;
+ struct winsize ws;
+
+ hp = container_of(work, struct hvc_struct, tty_resize);
+
+ spin_lock_irqsave(&hp->lock, hvc_flags);
+ if (!hp->tty) {
+ spin_unlock_irqrestore(&hp->lock, hvc_flags);
+ return;
+ }
+ ws = hp->ws;
+ tty = tty_kref_get(hp->tty);
+ spin_unlock_irqrestore(&hp->lock, hvc_flags);
+
+ tty_do_resize(tty, &ws);
+ tty_kref_put(tty);
+}
+
+/*
+ * This is actually a contract between the driver and the tty layer outlining
+ * how much write room the driver can guarantee 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 hp->outbuf_size - hp->n_outbuf;
+}
+
+static int hvc_chars_in_buffer(struct tty_struct *tty)
+{
+ struct hvc_struct *hp = tty->driver_data;
+
+ if (!hp)
+ return 0;
+ return hp->n_outbuf;
+}
+
+/*
+ * timeout will vary between the MIN and MAX values defined here. By default
+ * and during console activity we will use a default MIN_TIMEOUT of 10. When
+ * the console is idle, we increase the timeout value on each pass through
+ * msleep until we reach the max. This may be noticeable as a brief (average
+ * one second) delay on the console before the console responds to input when
+ * there has been no input for some time.
+ */
+#define MIN_TIMEOUT (10)
+#define MAX_TIMEOUT (2000)
+static u32 timeout = MIN_TIMEOUT;
+
+#define HVC_POLL_READ 0x00000001
+#define HVC_POLL_WRITE 0x00000002
+
+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;
+ int written_total = 0;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ /* Push pending writes */
+ if (hp->n_outbuf > 0)
+ written_total = hvc_push(hp);
+
+ /* Reschedule us if still some write pending */
+ if (hp->n_outbuf > 0) {
+ poll_mask |= HVC_POLL_WRITE;
+ /* If hvc_push() was not able to write, sleep a few msecs */
+ timeout = (written_total) ? 0 : MIN_TIMEOUT;
+ }
+
+ /* No tty attached, just skip */
+ tty = tty_kref_get(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 notifier driven and aren't throttled, we always
+ * request a reschedule
+ */
+ if (!hp->irq_requested)
+ poll_mask |= HVC_POLL_READ;
+
+ /* Read data if any */
+ for (;;) {
+ int count = tty_buffer_request_room(tty, N_INBUF);
+
+ /* If flip is full, just reschedule a later read */
+ if (count == 0) {
+ poll_mask |= HVC_POLL_READ;
+ break;
+ }
+
+ n = hp->ops->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);
+ } else if ( n == -EAGAIN ) {
+ /*
+ * Some back-ends can only ensure a certain min
+ * num of bytes read, which may be > 'count'.
+ * Let the tty clear the flip buff to make room.
+ */
+ poll_mask |= HVC_POLL_READ;
+ }
+ break;
+ }
+ for (i = 0; i < n; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+ if (hp->index == hvc_console.index) {
+ /* Handle the SysRq Hack */
+ /* XXX should support a sequence */
+ if (buf[i] == '\x0f') { /* ^O */
+ /* if ^O is pressed again, reset
+ * sysrq_pressed and flip ^O char */
+ sysrq_pressed = !sysrq_pressed;
+ if (sysrq_pressed)
+ continue;
+ } else if (sysrq_pressed) {
+ handle_sysrq(buf[i]);
+ sysrq_pressed = 0;
+ continue;
+ }
+ }
+#endif /* CONFIG_MAGIC_SYSRQ */
+ tty_insert_flip_char(tty, buf[i], 0);
+ }
+
+ read_total += n;
+ }
+ throttled:
+ /* Wakeup write queue if necessary */
+ if (hp->do_wakeup) {
+ hp->do_wakeup = 0;
+ tty_wakeup(tty);
+ }
+ bail:
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (read_total) {
+ /* Activity is occurring, so reset the polling backoff value to
+ a minimum for performance. */
+ timeout = MIN_TIMEOUT;
+
+ tty_flip_buffer_push(tty);
+ }
+ if (tty)
+ tty_kref_put(tty);
+
+ return poll_mask;
+}
+EXPORT_SYMBOL_GPL(hvc_poll);
+
+/**
+ * __hvc_resize() - Update terminal window size information.
+ * @hp: HVC console pointer
+ * @ws: Terminal window size structure
+ *
+ * Stores the specified window size information in the hvc structure of @hp.
+ * The function schedule the tty resize update.
+ *
+ * Locking: Locking free; the function MUST be called holding hp->lock
+ */
+void __hvc_resize(struct hvc_struct *hp, struct winsize ws)
+{
+ hp->ws = ws;
+ schedule_work(&hp->tty_resize);
+}
+EXPORT_SYMBOL_GPL(__hvc_resize);
+
+/*
+ * This kthread is either polling or interrupt driven. This is determined by
+ * calling hvc_poll() who determines whether a console adapter support
+ * interrupts.
+ */
+static int khvcd(void *unused)
+{
+ int poll_mask;
+ struct hvc_struct *hp;
+
+ set_freezable();
+ do {
+ poll_mask = 0;
+ hvc_kicked = 0;
+ try_to_freeze();
+ wmb();
+ if (!cpus_are_in_xmon()) {
+ spin_lock(&hvc_structs_lock);
+ list_for_each_entry(hp, &hvc_structs, next) {
+ poll_mask |= hvc_poll(hp);
+ }
+ spin_unlock(&hvc_structs_lock);
+ } else
+ poll_mask |= HVC_POLL_READ;
+ if (hvc_kicked)
+ continue;
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!hvc_kicked) {
+ if (poll_mask == 0)
+ schedule();
+ else {
+ if (timeout < MAX_TIMEOUT)
+ timeout += (timeout >> 6) + 1;
+
+ msleep_interruptible(timeout);
+ }
+ }
+ __set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+static int hvc_tiocmget(struct tty_struct *tty)
+{
+ struct hvc_struct *hp = tty->driver_data;
+
+ if (!hp || !hp->ops->tiocmget)
+ return -EINVAL;
+ return hp->ops->tiocmget(hp);
+}
+
+static int hvc_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct hvc_struct *hp = tty->driver_data;
+
+ if (!hp || !hp->ops->tiocmset)
+ return -EINVAL;
+ return hp->ops->tiocmset(hp, set, clear);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+int hvc_poll_init(struct tty_driver *driver, int line, char *options)
+{
+ return 0;
+}
+
+static int hvc_poll_get_char(struct tty_driver *driver, int line)
+{
+ struct tty_struct *tty = driver->ttys[0];
+ struct hvc_struct *hp = tty->driver_data;
+ int n;
+ char ch;
+
+ n = hp->ops->get_chars(hp->vtermno, &ch, 1);
+
+ if (n == 0)
+ return NO_POLL_CHAR;
+
+ return ch;
+}
+
+static void hvc_poll_put_char(struct tty_driver *driver, int line, char ch)
+{
+ struct tty_struct *tty = driver->ttys[0];
+ struct hvc_struct *hp = tty->driver_data;
+ int n;
+
+ do {
+ n = hp->ops->put_chars(hp->vtermno, &ch, 1);
+ } while (n <= 0);
+}
+#endif
+
+static const 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,
+ .tiocmget = hvc_tiocmget,
+ .tiocmset = hvc_tiocmset,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_init = hvc_poll_init,
+ .poll_get_char = hvc_poll_get_char,
+ .poll_put_char = hvc_poll_put_char,
+#endif
+};
+
+struct hvc_struct *hvc_alloc(uint32_t vtermno, int data,
+ const struct hv_ops *ops,
+ int outbuf_size)
+{
+ struct hvc_struct *hp;
+ int i;
+
+ /* We wait until a driver actually comes along */
+ if (!hvc_driver) {
+ int err = hvc_init();
+ if (err)
+ return ERR_PTR(err);
+ }
+
+ hp = kzalloc(ALIGN(sizeof(*hp), sizeof(long)) + outbuf_size,
+ GFP_KERNEL);
+ if (!hp)
+ return ERR_PTR(-ENOMEM);
+
+ hp->vtermno = vtermno;
+ hp->data = data;
+ hp->ops = ops;
+ hp->outbuf_size = outbuf_size;
+ hp->outbuf = &((char *)hp)[ALIGN(sizeof(*hp), sizeof(long))];
+
+ kref_init(&hp->kref);
+
+ INIT_WORK(&hp->tty_resize, hvc_set_winsz);
+ spin_lock_init(&hp->lock);
+ spin_lock(&hvc_structs_lock);
+
+ /*
+ * find index to use:
+ * see if this vterm id matches one registered for console.
+ */
+ for (i=0; i < MAX_NR_HVC_CONSOLES; i++)
+ if (vtermnos[i] == hp->vtermno &&
+ cons_ops[i] == hp->ops)
+ break;
+
+ /* no matching slot, just use a counter */
+ if (i >= MAX_NR_HVC_CONSOLES)
+ i = ++last_hvc;
+
+ hp->index = i;
+
+ list_add_tail(&(hp->next), &hvc_structs);
+ spin_unlock(&hvc_structs_lock);
+
+ return hp;
+}
+EXPORT_SYMBOL_GPL(hvc_alloc);
+
+int hvc_remove(struct hvc_struct *hp)
+{
+ unsigned long flags;
+ struct tty_struct *tty;
+
+ spin_lock_irqsave(&hp->lock, flags);
+ tty = tty_kref_get(hp->tty);
+
+ 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 kref instance
+ * was initialized using kref_init(). Let the last holder of this
+ * kref cause it to be removed, which will probably be the tty_vhangup
+ * below.
+ */
+ kref_put(&hp->kref, destroy_hvc_struct);
+
+ /*
+ * This function call will auto chain call hvc_hangup.
+ */
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hvc_remove);
+
+/* Driver initialization: called as soon as someone uses hvc_alloc(). */
+static int hvc_init(void)
+{
+ struct tty_driver *drv;
+ int err;
+
+ /* We need more than hvc_count adapters due to hotplug additions. */
+ drv = alloc_tty_driver(HVC_ALLOC_TTY_ADAPTERS);
+ if (!drv) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ drv->owner = THIS_MODULE;
+ drv->driver_name = "hvc";
+ drv->name = "hvc";
+ drv->major = HVC_MAJOR;
+ drv->minor_start = HVC_MINOR;
+ drv->type = TTY_DRIVER_TYPE_SYSTEM;
+ drv->init_termios = tty_std_termios;
+ drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
+ tty_set_operations(drv, &hvc_ops);
+
+ /* 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)) {
+ printk(KERN_ERR "Couldn't create kthread for console.\n");
+ err = PTR_ERR(hvc_task);
+ goto put_tty;
+ }
+
+ err = tty_register_driver(drv);
+ if (err) {
+ printk(KERN_ERR "Couldn't register hvc console driver\n");
+ goto stop_thread;
+ }
+
+ /*
+ * Make sure tty is fully registered before allowing it to be
+ * found by hvc_console_device.
+ */
+ smp_mb();
+ hvc_driver = drv;
+ return 0;
+
+stop_thread:
+ kthread_stop(hvc_task);
+ hvc_task = NULL;
+put_tty:
+ put_tty_driver(drv);
+out:
+ return err;
+}
+
+/* This isn't particularly necessary due to this being a console driver
+ * but it is nice to be thorough.
+ */
+static void __exit hvc_exit(void)
+{
+ if (hvc_driver) {
+ kthread_stop(hvc_task);
+
+ tty_unregister_driver(hvc_driver);
+ /* return tty_struct instances allocated in hvc_init(). */
+ put_tty_driver(hvc_driver);
+ unregister_console(&hvc_console);
+ }
+}
+module_exit(hvc_exit);
diff --git a/drivers/tty/hvc/hvc_console.h b/drivers/tty/hvc/hvc_console.h
new file mode 100644
index 00000000000..c335a1492a5
--- /dev/null
+++ b/drivers/tty/hvc/hvc_console.h
@@ -0,0 +1,123 @@
+/*
+ * hvc_console.h
+ * Copyright (C) 2005 IBM Corporation
+ *
+ * Author(s):
+ * Ryan S. Arnold <rsa@us.ibm.com>
+ *
+ * hvc_console header information:
+ * moved here from arch/powerpc/include/asm/hvconsole.h
+ * and drivers/char/hvc_console.c
+ *
+ * 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
+ */
+
+#ifndef HVC_CONSOLE_H
+#define HVC_CONSOLE_H
+#include <linux/kref.h>
+#include <linux/tty.h>
+#include <linux/spinlock.h>
+
+/*
+ * This is the max number of console adapters that can/will be found as
+ * console devices on first stage console init. Any number beyond this range
+ * can't be used as a console device but is still a valid tty device.
+ */
+#define MAX_NR_HVC_CONSOLES 16
+
+/*
+ * 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
+
+struct hvc_struct {
+ spinlock_t lock;
+ int index;
+ struct tty_struct *tty;
+ int count;
+ int do_wakeup;
+ char *outbuf;
+ int outbuf_size;
+ int n_outbuf;
+ uint32_t vtermno;
+ const struct hv_ops *ops;
+ int irq_requested;
+ int data;
+ struct winsize ws;
+ struct work_struct tty_resize;
+ struct list_head next;
+ struct kref kref; /* ref count & hvc_struct lifetime */
+};
+
+/* implemented by a low level driver */
+struct hv_ops {
+ int (*get_chars)(uint32_t vtermno, char *buf, int count);
+ int (*put_chars)(uint32_t vtermno, const char *buf, int count);
+
+ /* Callbacks for notification. Called in open, close and hangup */
+ int (*notifier_add)(struct hvc_struct *hp, int irq);
+ void (*notifier_del)(struct hvc_struct *hp, int irq);
+ void (*notifier_hangup)(struct hvc_struct *hp, int irq);
+
+ /* tiocmget/set implementation */
+ int (*tiocmget)(struct hvc_struct *hp);
+ int (*tiocmset)(struct hvc_struct *hp, unsigned int set, unsigned int clear);
+};
+
+/* Register a vterm and a slot index for use as a console (console_init) */
+extern int hvc_instantiate(uint32_t vtermno, int index,
+ const struct hv_ops *ops);
+
+/* register a vterm for hvc tty operation (module_init or hotplug add) */
+extern struct hvc_struct * hvc_alloc(uint32_t vtermno, int data,
+ const struct hv_ops *ops, int outbuf_size);
+/* remove a vterm from hvc tty operation (module_exit or hotplug remove) */
+extern int hvc_remove(struct hvc_struct *hp);
+
+/* data available */
+int hvc_poll(struct hvc_struct *hp);
+void hvc_kick(void);
+
+/* Resize hvc tty terminal window */
+extern void __hvc_resize(struct hvc_struct *hp, struct winsize ws);
+
+static inline void hvc_resize(struct hvc_struct *hp, struct winsize ws)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hp->lock, flags);
+ __hvc_resize(hp, ws);
+ spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+/* default notifier for irq based notification */
+extern int notifier_add_irq(struct hvc_struct *hp, int data);
+extern void notifier_del_irq(struct hvc_struct *hp, int data);
+extern void notifier_hangup_irq(struct hvc_struct *hp, int data);
+
+
+#if defined(CONFIG_XMON) && defined(CONFIG_SMP)
+#include <asm/xmon.h>
+#else
+static inline int cpus_are_in_xmon(void)
+{
+ return 0;
+}
+#endif
+
+#endif // HVC_CONSOLE_H
diff --git a/drivers/tty/hvc/hvc_dcc.c b/drivers/tty/hvc/hvc_dcc.c
new file mode 100644
index 00000000000..44fbebab507
--- /dev/null
+++ b/drivers/tty/hvc/hvc_dcc.c
@@ -0,0 +1,106 @@
+/* Copyright (c) 2010, Code Aurora Forum. 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 version 2 and
+ * only 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+
+#include <asm/processor.h>
+
+#include "hvc_console.h"
+
+/* DCC Status Bits */
+#define DCC_STATUS_RX (1 << 30)
+#define DCC_STATUS_TX (1 << 29)
+
+static inline u32 __dcc_getstatus(void)
+{
+ u32 __ret;
+ asm volatile("mrc p14, 0, %0, c0, c1, 0 @ read comms ctrl reg"
+ : "=r" (__ret) : : "cc");
+
+ return __ret;
+}
+
+
+static inline char __dcc_getchar(void)
+{
+ char __c;
+
+ asm volatile("mrc p14, 0, %0, c0, c5, 0 @ read comms data reg"
+ : "=r" (__c));
+ isb();
+
+ return __c;
+}
+
+static inline void __dcc_putchar(char c)
+{
+ asm volatile("mcr p14, 0, %0, c0, c5, 0 @ write a char"
+ : /* no output register */
+ : "r" (c));
+ isb();
+}
+
+static int hvc_dcc_put_chars(uint32_t vt, const char *buf, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ while (__dcc_getstatus() & DCC_STATUS_TX)
+ cpu_relax();
+
+ __dcc_putchar(buf[i]);
+ }
+
+ return count;
+}
+
+static int hvc_dcc_get_chars(uint32_t vt, char *buf, int count)
+{
+ int i;
+
+ for (i = 0; i < count; ++i)
+ if (__dcc_getstatus() & DCC_STATUS_RX)
+ buf[i] = __dcc_getchar();
+ else
+ break;
+
+ return i;
+}
+
+static const struct hv_ops hvc_dcc_get_put_ops = {
+ .get_chars = hvc_dcc_get_chars,
+ .put_chars = hvc_dcc_put_chars,
+};
+
+static int __init hvc_dcc_console_init(void)
+{
+ hvc_instantiate(0, 0, &hvc_dcc_get_put_ops);
+ return 0;
+}
+console_initcall(hvc_dcc_console_init);
+
+static int __init hvc_dcc_init(void)
+{
+ hvc_alloc(0, 0, &hvc_dcc_get_put_ops, 128);
+ return 0;
+}
+device_initcall(hvc_dcc_init);
diff --git a/drivers/tty/hvc/hvc_irq.c b/drivers/tty/hvc/hvc_irq.c
new file mode 100644
index 00000000000..c9adb0559f6
--- /dev/null
+++ b/drivers/tty/hvc/hvc_irq.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright IBM Corp. 2001,2008
+ *
+ * This file contains the IRQ specific code for hvc_console
+ *
+ */
+
+#include <linux/interrupt.h>
+
+#include "hvc_console.h"
+
+static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance)
+{
+ /* if hvc_poll request a repoll, then kick the hvcd thread */
+ if (hvc_poll(dev_instance))
+ hvc_kick();
+ return IRQ_HANDLED;
+}
+
+/*
+ * For IRQ based systems these callbacks can be used
+ */
+int notifier_add_irq(struct hvc_struct *hp, int irq)
+{
+ int rc;
+
+ if (!irq) {
+ hp->irq_requested = 0;
+ return 0;
+ }
+ rc = request_irq(irq, hvc_handle_interrupt, 0,
+ "hvc_console", hp);
+ if (!rc)
+ hp->irq_requested = 1;
+ return rc;
+}
+
+void notifier_del_irq(struct hvc_struct *hp, int irq)
+{
+ if (!hp->irq_requested)
+ return;
+ free_irq(irq, hp);
+ hp->irq_requested = 0;
+}
+
+void notifier_hangup_irq(struct hvc_struct *hp, int irq)
+{
+ notifier_del_irq(hp, irq);
+}
diff --git a/drivers/tty/hvc/hvc_iseries.c b/drivers/tty/hvc/hvc_iseries.c
new file mode 100644
index 00000000000..3f4a897bf4d
--- /dev/null
+++ b/drivers/tty/hvc/hvc_iseries.c
@@ -0,0 +1,599 @@
+/*
+ * iSeries vio driver interface to hvc_console.c
+ *
+ * This code is based heavily on hvc_vio.c and viocons.c
+ *
+ * Copyright (C) 2006 Stephen Rothwell, 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.
+ *
+ * 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 <stdarg.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/console.h>
+
+#include <asm/hvconsole.h>
+#include <asm/vio.h>
+#include <asm/prom.h>
+#include <asm/firmware.h>
+#include <asm/iseries/vio.h>
+#include <asm/iseries/hv_call.h>
+#include <asm/iseries/hv_lp_config.h>
+#include <asm/iseries/hv_lp_event.h>
+
+#include "hvc_console.h"
+
+#define VTTY_PORTS 10
+
+static DEFINE_SPINLOCK(consolelock);
+static DEFINE_SPINLOCK(consoleloglock);
+
+static const char hvc_driver_name[] = "hvc_console";
+
+#define IN_BUF_SIZE 200
+
+/*
+ * Our port information.
+ */
+static struct port_info {
+ HvLpIndex lp;
+ u64 seq; /* sequence number of last HV send */
+ u64 ack; /* last ack from HV */
+ struct hvc_struct *hp;
+ int in_start;
+ int in_end;
+ unsigned char in_buf[IN_BUF_SIZE];
+} port_info[VTTY_PORTS] = {
+ [ 0 ... VTTY_PORTS - 1 ] = {
+ .lp = HvLpIndexInvalid
+ }
+};
+
+#define viochar_is_console(pi) ((pi) == &port_info[0])
+
+static struct vio_device_id hvc_driver_table[] __devinitdata = {
+ {"serial", "IBM,iSeries-vty"},
+ { "", "" }
+};
+MODULE_DEVICE_TABLE(vio, hvc_driver_table);
+
+static 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);
+}
+
+/*
+ * Initialize the common fields in a charLpEvent
+ */
+static void init_data_event(struct viocharlpevent *viochar, HvLpIndex lp)
+{
+ struct HvLpEvent *hev = &viochar->event;
+
+ memset(viochar, 0, sizeof(struct viocharlpevent));
+
+ hev->flags = HV_LP_EVENT_VALID | HV_LP_EVENT_DEFERRED_ACK |
+ HV_LP_EVENT_INT;
+ hev->xType = HvLpEvent_Type_VirtualIo;
+ hev->xSubtype = viomajorsubtype_chario | viochardata;
+ hev->xSourceLp = HvLpConfig_getLpIndex();
+ hev->xTargetLp = lp;
+ hev->xSizeMinus1 = sizeof(struct viocharlpevent);
+ hev->xSourceInstanceId = viopath_sourceinst(lp);
+ hev->xTargetInstanceId = viopath_targetinst(lp);
+}
+
+static int get_chars(uint32_t vtermno, char *buf, int count)
+{
+ struct port_info *pi;
+ int n = 0;
+ unsigned long flags;
+
+ if (vtermno >= VTTY_PORTS)
+ return -EINVAL;
+ if (count == 0)
+ return 0;
+
+ pi = &port_info[vtermno];
+ spin_lock_irqsave(&consolelock, flags);
+
+ if (pi->in_end == 0)
+ goto done;
+
+ n = pi->in_end - pi->in_start;
+ if (n > count)
+ n = count;
+ memcpy(buf, &pi->in_buf[pi->in_start], n);
+ pi->in_start += n;
+ if (pi->in_start == pi->in_end) {
+ pi->in_start = 0;
+ pi->in_end = 0;
+ }
+done:
+ spin_unlock_irqrestore(&consolelock, flags);
+ return n;
+}
+
+static int put_chars(uint32_t vtermno, const char *buf, int count)
+{
+ struct viocharlpevent *viochar;
+ struct port_info *pi;
+ HvLpEvent_Rc hvrc;
+ unsigned long flags;
+ int sent = 0;
+
+ if (vtermno >= VTTY_PORTS)
+ return -EINVAL;
+
+ pi = &port_info[vtermno];
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ if (viochar_is_console(pi) && !viopath_isactive(pi->lp)) {
+ HvCall_writeLogBuffer(buf, count);
+ sent = count;
+ goto done;
+ }
+
+ viochar = vio_get_event_buffer(viomajorsubtype_chario);
+ if (viochar == NULL) {
+ hvlog("\n\rviocons: Can't get viochar buffer.");
+ goto done;
+ }
+
+ while ((count > 0) && ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
+ int len;
+
+ len = (count > VIOCHAR_MAX_DATA) ? VIOCHAR_MAX_DATA : count;
+
+ if (viochar_is_console(pi))
+ HvCall_writeLogBuffer(buf, len);
+
+ init_data_event(viochar, pi->lp);
+
+ viochar->len = len;
+ viochar->event.xCorrelationToken = pi->seq++;
+ viochar->event.xSizeMinus1 =
+ offsetof(struct viocharlpevent, data) + len;
+
+ memcpy(viochar->data, buf, len);
+
+ hvrc = HvCallEvent_signalLpEvent(&viochar->event);
+ if (hvrc)
+ hvlog("\n\rerror sending event! return code %d\n\r",
+ (int)hvrc);
+ sent += len;
+ count -= len;
+ buf += len;
+ }
+
+ vio_free_event_buffer(viomajorsubtype_chario, viochar);
+done:
+ spin_unlock_irqrestore(&consolelock, flags);
+ return sent;
+}
+
+static const struct hv_ops hvc_get_put_ops = {
+ .get_chars = get_chars,
+ .put_chars = put_chars,
+ .notifier_add = notifier_add_irq,
+ .notifier_del = notifier_del_irq,
+ .notifier_hangup = notifier_hangup_irq,
+};
+
+static int __devinit hvc_vio_probe(struct vio_dev *vdev,
+ const struct vio_device_id *id)
+{
+ struct hvc_struct *hp;
+ struct port_info *pi;
+
+ /* probed with invalid parameters. */
+ if (!vdev || !id)
+ return -EPERM;
+
+ if (vdev->unit_address >= VTTY_PORTS)
+ return -ENODEV;
+
+ pi = &port_info[vdev->unit_address];
+
+ hp = hvc_alloc(vdev->unit_address, vdev->irq, &hvc_get_put_ops,
+ VIOCHAR_MAX_DATA);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+ pi->hp = hp;
+ dev_set_drvdata(&vdev->dev, pi);
+
+ return 0;
+}
+
+static int __devexit hvc_vio_remove(struct vio_dev *vdev)
+{
+ struct port_info *pi = dev_get_drvdata(&vdev->dev);
+ struct hvc_struct *hp = pi->hp;
+
+ return hvc_remove(hp);
+}
+
+static struct vio_driver hvc_vio_driver = {
+ .id_table = hvc_driver_table,
+ .probe = hvc_vio_probe,
+ .remove = __devexit_p(hvc_vio_remove),
+ .driver = {
+ .name = hvc_driver_name,
+ .owner = THIS_MODULE,
+ }
+};
+
+static void hvc_open_event(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 (hvlpevent_is_ack(event)) {
+ if (port >= VTTY_PORTS)
+ return;
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ 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(KERN_WARNING
+ "hvc: 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(KERN_WARNING
+ "hvc: weird...got open ack without atomic\n");
+ return;
+ }
+
+ /* This had better require an ack, otherwise complain */
+ if (!hvlpevent_need_ack(event)) {
+ printk(KERN_WARNING "hvc: viocharopen without ack bit!\n");
+ return;
+ }
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ /* 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
+ * the consolelock 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;
+ }
+ }
+
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ if (reject == 1)
+ printk(KERN_WARNING "hvc: open rejected: bad virtual tty.\n");
+ else if (reject == 2)
+ printk(KERN_WARNING "hvc: 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.
+ */
+static void hvc_close_event(struct HvLpEvent *event)
+{
+ unsigned long flags;
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ u8 port = cevent->virtual_device;
+
+ if (!hvlpevent_is_int(event)) {
+ printk(KERN_WARNING
+ "hvc: got unexpected close acknowledgement\n");
+ return;
+ }
+
+ if (port >= VTTY_PORTS) {
+ printk(KERN_WARNING
+ "hvc: close message from invalid virtual device.\n");
+ return;
+ }
+
+ /* For closes, just mark the console partition invalid */
+ spin_lock_irqsave(&consolelock, flags);
+
+ if (port_info[port].lp == event->xSourceLp)
+ port_info[port].lp = HvLpIndexInvalid;
+
+ spin_unlock_irqrestore(&consolelock, flags);
+}
+
+static void hvc_data_event(struct HvLpEvent *event)
+{
+ unsigned long flags;
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ struct port_info *pi;
+ int n;
+ u8 port = cevent->virtual_device;
+
+ if (port >= VTTY_PORTS) {
+ printk(KERN_WARNING "hvc: data on invalid virtual device %d\n",
+ port);
+ return;
+ }
+ if (cevent->len == 0)
+ return;
+
+ /*
+ * 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.
+ */
+ pi = &port_info[port];
+ if (pi->lp != event->xSourceLp)
+ return;
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ n = IN_BUF_SIZE - pi->in_end;
+ if (n > cevent->len)
+ n = cevent->len;
+ if (n > 0) {
+ memcpy(&pi->in_buf[pi->in_end], cevent->data, n);
+ pi->in_end += n;
+ }
+ spin_unlock_irqrestore(&consolelock, flags);
+ if (n == 0)
+ printk(KERN_WARNING "hvc: input buffer overflow\n");
+}
+
+static void hvc_ack_event(struct HvLpEvent *event)
+{
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ unsigned long flags;
+ u8 port = cevent->virtual_device;
+
+ if (port >= VTTY_PORTS) {
+ printk(KERN_WARNING "hvc: data on invalid virtual device\n");
+ return;
+ }
+
+ spin_lock_irqsave(&consolelock, flags);
+ port_info[port].ack = event->xCorrelationToken;
+ spin_unlock_irqrestore(&consolelock, flags);
+}
+
+static void hvc_config_event(struct HvLpEvent *event)
+{
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+
+ if (cevent->data[0] == 0x01)
+ printk(KERN_INFO "hvc: window resized to %d: %d: %d: %d\n",
+ cevent->data[1], cevent->data[2],
+ cevent->data[3], cevent->data[4]);
+ else
+ printk(KERN_WARNING "hvc: unknown config event\n");
+}
+
+static void hvc_handle_event(struct HvLpEvent *event)
+{
+ int charminor;
+
+ if (event == NULL)
+ return;
+
+ charminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK;
+ switch (charminor) {
+ case viocharopen:
+ hvc_open_event(event);
+ break;
+ case viocharclose:
+ hvc_close_event(event);
+ break;
+ case viochardata:
+ hvc_data_event(event);
+ break;
+ case viocharack:
+ hvc_ack_event(event);
+ break;
+ case viocharconfig:
+ hvc_config_event(event);
+ break;
+ default:
+ if (hvlpevent_is_int(event) && hvlpevent_need_ack(event)) {
+ event->xRc = HvLpEvent_Rc_InvalidSubtype;
+ HvCallEvent_ackLpEvent(event);
+ }
+ }
+}
+
+static int __init 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 int __init hvc_vio_init(void)
+{
+ atomic_t wait_flag;
+ int rc;
+
+ if (!firmware_has_feature(FW_FEATURE_ISERIES))
+ return -EIO;
+
+ /* +2 for fudge */
+ rc = viopath_open(HvLpConfig_getPrimaryLpIndex(),
+ viomajorsubtype_chario, VIOCHAR_WINDOW + 2);
+ if (rc)
+ printk(KERN_WARNING "hvc: 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(KERN_INFO "hvc: open path to hosting (%d)\n",
+ viopath_hostLp);
+ rc = viopath_open(viopath_hostLp, viomajorsubtype_chario,
+ VIOCHAR_WINDOW + 2); /* +2 for fudge */
+ if (rc)
+ printk(KERN_WARNING
+ "error opening to partition %d: %d\n",
+ viopath_hostLp, rc);
+ }
+
+ if (vio_setHandler(viomajorsubtype_chario, hvc_handle_event) < 0)
+ printk(KERN_WARNING
+ "hvc: 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, &wait_flag) == 0)) {
+ printk(KERN_INFO "hvc: 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(), &wait_flag) == 0)) {
+ printk(KERN_INFO "hvc: opening console to primary partition\n");
+ while (atomic_read(&wait_flag) == 0)
+ mb();
+ }
+
+ /* Register as a vio device to receive callbacks */
+ rc = vio_register_driver(&hvc_vio_driver);
+
+ return rc;
+}
+module_init(hvc_vio_init); /* after drivers/char/hvc_console.c */
+
+static void __exit hvc_vio_exit(void)
+{
+ vio_unregister_driver(&hvc_vio_driver);
+}
+module_exit(hvc_vio_exit);
+
+/* the device tree order defines our numbering */
+static int __init hvc_find_vtys(void)
+{
+ struct device_node *vty;
+ int num_found = 0;
+
+ for (vty = of_find_node_by_name(NULL, "vty"); vty != NULL;
+ vty = of_find_node_by_name(vty, "vty")) {
+ const uint32_t *vtermno;
+
+ /* We have statically defined space for only a certain number
+ * of console adapters.
+ */
+ if ((num_found >= MAX_NR_HVC_CONSOLES) ||
+ (num_found >= VTTY_PORTS)) {
+ of_node_put(vty);
+ break;
+ }
+
+ vtermno = of_get_property(vty, "reg", NULL);
+ if (!vtermno)
+ continue;
+
+ if (!of_device_is_compatible(vty, "IBM,iSeries-vty"))
+ continue;
+
+ if (num_found == 0)
+ add_preferred_console("hvc", 0, NULL);
+ hvc_instantiate(*vtermno, num_found, &hvc_get_put_ops);
+ ++num_found;
+ }
+
+ return num_found;
+}
+console_initcall(hvc_find_vtys);
diff --git a/drivers/tty/hvc/hvc_iucv.c b/drivers/tty/hvc/hvc_iucv.c
new file mode 100644
index 00000000000..b6f7d52f7c3
--- /dev/null
+++ b/drivers/tty/hvc/hvc_iucv.c
@@ -0,0 +1,1337 @@
+/*
+ * hvc_iucv.c - z/VM IUCV hypervisor console (HVC) device driver
+ *
+ * This HVC device driver provides terminal access using
+ * z/VM IUCV communication paths.
+ *
+ * Copyright IBM Corp. 2008, 2009
+ *
+ * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
+ */
+#define KMSG_COMPONENT "hvc_iucv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <asm/ebcdic.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/mempool.h>
+#include <linux/moduleparam.h>
+#include <linux/tty.h>
+#include <linux/wait.h>
+#include <net/iucv/iucv.h>
+
+#include "hvc_console.h"
+
+
+/* General device driver settings */
+#define HVC_IUCV_MAGIC 0xc9e4c3e5
+#define MAX_HVC_IUCV_LINES HVC_ALLOC_TTY_ADAPTERS
+#define MEMPOOL_MIN_NR (PAGE_SIZE / sizeof(struct iucv_tty_buffer)/4)
+
+/* IUCV TTY message */
+#define MSG_VERSION 0x02 /* Message version */
+#define MSG_TYPE_ERROR 0x01 /* Error message */
+#define MSG_TYPE_TERMENV 0x02 /* Terminal environment variable */
+#define MSG_TYPE_TERMIOS 0x04 /* Terminal IO struct update */
+#define MSG_TYPE_WINSIZE 0x08 /* Terminal window size update */
+#define MSG_TYPE_DATA 0x10 /* Terminal data */
+
+struct iucv_tty_msg {
+ u8 version; /* Message version */
+ u8 type; /* Message type */
+#define MSG_MAX_DATALEN ((u16)(~0))
+ u16 datalen; /* Payload length */
+ u8 data[]; /* Payload buffer */
+} __attribute__((packed));
+#define MSG_SIZE(s) ((s) + offsetof(struct iucv_tty_msg, data))
+
+enum iucv_state_t {
+ IUCV_DISCONN = 0,
+ IUCV_CONNECTED = 1,
+ IUCV_SEVERED = 2,
+};
+
+enum tty_state_t {
+ TTY_CLOSED = 0,
+ TTY_OPENED = 1,
+};
+
+struct hvc_iucv_private {
+ struct hvc_struct *hvc; /* HVC struct reference */
+ u8 srv_name[8]; /* IUCV service name (ebcdic) */
+ unsigned char is_console; /* Linux console usage flag */
+ enum iucv_state_t iucv_state; /* IUCV connection status */
+ enum tty_state_t tty_state; /* TTY status */
+ struct iucv_path *path; /* IUCV path pointer */
+ spinlock_t lock; /* hvc_iucv_private lock */
+#define SNDBUF_SIZE (PAGE_SIZE) /* must be < MSG_MAX_DATALEN */
+ void *sndbuf; /* send buffer */
+ size_t sndbuf_len; /* length of send buffer */
+#define QUEUE_SNDBUF_DELAY (HZ / 25)
+ struct delayed_work sndbuf_work; /* work: send iucv msg(s) */
+ wait_queue_head_t sndbuf_waitq; /* wait for send completion */
+ struct list_head tty_outqueue; /* outgoing IUCV messages */
+ struct list_head tty_inqueue; /* incoming IUCV messages */
+ struct device *dev; /* device structure */
+};
+
+struct iucv_tty_buffer {
+ struct list_head list; /* list pointer */
+ struct iucv_message msg; /* store an IUCV message */
+ size_t offset; /* data buffer offset */
+ struct iucv_tty_msg *mbuf; /* buffer to store input/output data */
+};
+
+/* IUCV callback handler */
+static int hvc_iucv_path_pending(struct iucv_path *, u8[8], u8[16]);
+static void hvc_iucv_path_severed(struct iucv_path *, u8[16]);
+static void hvc_iucv_msg_pending(struct iucv_path *, struct iucv_message *);
+static void hvc_iucv_msg_complete(struct iucv_path *, struct iucv_message *);
+
+
+/* Kernel module parameter: use one terminal device as default */
+static unsigned long hvc_iucv_devices = 1;
+
+/* Array of allocated hvc iucv tty lines... */
+static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES];
+#define IUCV_HVC_CON_IDX (0)
+/* List of z/VM user ID filter entries (struct iucv_vmid_filter) */
+#define MAX_VMID_FILTER (500)
+static size_t hvc_iucv_filter_size;
+static void *hvc_iucv_filter;
+static const char *hvc_iucv_filter_string;
+static DEFINE_RWLOCK(hvc_iucv_filter_lock);
+
+/* Kmem cache and mempool for iucv_tty_buffer elements */
+static struct kmem_cache *hvc_iucv_buffer_cache;
+static mempool_t *hvc_iucv_mempool;
+
+/* IUCV handler callback functions */
+static struct iucv_handler hvc_iucv_handler = {
+ .path_pending = hvc_iucv_path_pending,
+ .path_severed = hvc_iucv_path_severed,
+ .message_complete = hvc_iucv_msg_complete,
+ .message_pending = hvc_iucv_msg_pending,
+};
+
+
+/**
+ * hvc_iucv_get_private() - Return a struct hvc_iucv_private instance.
+ * @num: The HVC virtual terminal number (vtermno)
+ *
+ * This function returns the struct hvc_iucv_private instance that corresponds
+ * to the HVC virtual terminal number specified as parameter @num.
+ */
+struct hvc_iucv_private *hvc_iucv_get_private(uint32_t num)
+{
+ if ((num < HVC_IUCV_MAGIC) || (num - HVC_IUCV_MAGIC > hvc_iucv_devices))
+ return NULL;
+ return hvc_iucv_table[num - HVC_IUCV_MAGIC];
+}
+
+/**
+ * alloc_tty_buffer() - Return a new struct iucv_tty_buffer element.
+ * @size: Size of the internal buffer used to store data.
+ * @flags: Memory allocation flags passed to mempool.
+ *
+ * This function allocates a new struct iucv_tty_buffer element and, optionally,
+ * allocates an internal data buffer with the specified size @size.
+ * The internal data buffer is always allocated with GFP_DMA which is
+ * required for receiving and sending data with IUCV.
+ * Note: The total message size arises from the internal buffer size and the
+ * members of the iucv_tty_msg structure.
+ * The function returns NULL if memory allocation has failed.
+ */
+static struct iucv_tty_buffer *alloc_tty_buffer(size_t size, gfp_t flags)
+{
+ struct iucv_tty_buffer *bufp;
+
+ bufp = mempool_alloc(hvc_iucv_mempool, flags);
+ if (!bufp)
+ return NULL;
+ memset(bufp, 0, sizeof(*bufp));
+
+ if (size > 0) {
+ bufp->msg.length = MSG_SIZE(size);
+ bufp->mbuf = kmalloc(bufp->msg.length, flags | GFP_DMA);
+ if (!bufp->mbuf) {
+ mempool_free(bufp, hvc_iucv_mempool);
+ return NULL;
+ }
+ bufp->mbuf->version = MSG_VERSION;
+ bufp->mbuf->type = MSG_TYPE_DATA;
+ bufp->mbuf->datalen = (u16) size;
+ }
+ return bufp;
+}
+
+/**
+ * destroy_tty_buffer() - destroy struct iucv_tty_buffer element.
+ * @bufp: Pointer to a struct iucv_tty_buffer element, SHALL NOT be NULL.
+ */
+static void destroy_tty_buffer(struct iucv_tty_buffer *bufp)
+{
+ kfree(bufp->mbuf);
+ mempool_free(bufp, hvc_iucv_mempool);
+}
+
+/**
+ * destroy_tty_buffer_list() - call destroy_tty_buffer() for each list element.
+ * @list: List containing struct iucv_tty_buffer elements.
+ */
+static void destroy_tty_buffer_list(struct list_head *list)
+{
+ struct iucv_tty_buffer *ent, *next;
+
+ list_for_each_entry_safe(ent, next, list, list) {
+ list_del(&ent->list);
+ destroy_tty_buffer(ent);
+ }
+}
+
+/**
+ * hvc_iucv_write() - Receive IUCV message & write data to HVC buffer.
+ * @priv: Pointer to struct hvc_iucv_private
+ * @buf: HVC buffer for writing received terminal data.
+ * @count: HVC buffer size.
+ * @has_more_data: Pointer to an int variable.
+ *
+ * The function picks up pending messages from the input queue and receives
+ * the message data that is then written to the specified buffer @buf.
+ * If the buffer size @count is less than the data message size, the
+ * message is kept on the input queue and @has_more_data is set to 1.
+ * If all message data has been written, the message is removed from
+ * the input queue.
+ *
+ * The function returns the number of bytes written to the terminal, zero if
+ * there are no pending data messages available or if there is no established
+ * IUCV path.
+ * If the IUCV path has been severed, then -EPIPE is returned to cause a
+ * hang up (that is issued by the HVC layer).
+ */
+static int hvc_iucv_write(struct hvc_iucv_private *priv,
+ char *buf, int count, int *has_more_data)
+{
+ struct iucv_tty_buffer *rb;
+ int written;
+ int rc;
+
+ /* immediately return if there is no IUCV connection */
+ if (priv->iucv_state == IUCV_DISCONN)
+ return 0;
+
+ /* if the IUCV path has been severed, return -EPIPE to inform the
+ * HVC layer to hang up the tty device. */
+ if (priv->iucv_state == IUCV_SEVERED)
+ return -EPIPE;
+
+ /* check if there are pending messages */
+ if (list_empty(&priv->tty_inqueue))
+ return 0;
+
+ /* receive an iucv message and flip data to the tty (ldisc) */
+ rb = list_first_entry(&priv->tty_inqueue, struct iucv_tty_buffer, list);
+
+ written = 0;
+ if (!rb->mbuf) { /* message not yet received ... */
+ /* allocate mem to store msg data; if no memory is available
+ * then leave the buffer on the list and re-try later */
+ rb->mbuf = kmalloc(rb->msg.length, GFP_ATOMIC | GFP_DMA);
+ if (!rb->mbuf)
+ return -ENOMEM;
+
+ rc = __iucv_message_receive(priv->path, &rb->msg, 0,
+ rb->mbuf, rb->msg.length, NULL);
+ switch (rc) {
+ case 0: /* Successful */
+ break;
+ case 2: /* No message found */
+ case 9: /* Message purged */
+ break;
+ default:
+ written = -EIO;
+ }
+ /* remove buffer if an error has occurred or received data
+ * is not correct */
+ if (rc || (rb->mbuf->version != MSG_VERSION) ||
+ (rb->msg.length != MSG_SIZE(rb->mbuf->datalen)))
+ goto out_remove_buffer;
+ }
+
+ switch (rb->mbuf->type) {
+ case MSG_TYPE_DATA:
+ written = min_t(int, rb->mbuf->datalen - rb->offset, count);
+ memcpy(buf, rb->mbuf->data + rb->offset, written);
+ if (written < (rb->mbuf->datalen - rb->offset)) {
+ rb->offset += written;
+ *has_more_data = 1;
+ goto out_written;
+ }
+ break;
+
+ case MSG_TYPE_WINSIZE:
+ if (rb->mbuf->datalen != sizeof(struct winsize))
+ break;
+ /* The caller must ensure that the hvc is locked, which
+ * is the case when called from hvc_iucv_get_chars() */
+ __hvc_resize(priv->hvc, *((struct winsize *) rb->mbuf->data));
+ break;
+
+ case MSG_TYPE_ERROR: /* ignored ... */
+ case MSG_TYPE_TERMENV: /* ignored ... */
+ case MSG_TYPE_TERMIOS: /* ignored ... */
+ break;
+ }
+
+out_remove_buffer:
+ list_del(&rb->list);
+ destroy_tty_buffer(rb);
+ *has_more_data = !list_empty(&priv->tty_inqueue);
+
+out_written:
+ return written;
+}
+
+/**
+ * hvc_iucv_get_chars() - HVC get_chars operation.
+ * @vtermno: HVC virtual terminal number.
+ * @buf: Pointer to a buffer to store data
+ * @count: Size of buffer available for writing
+ *
+ * The HVC thread calls this method to read characters from the back-end.
+ * If an IUCV communication path has been established, pending IUCV messages
+ * are received and data is copied into buffer @buf up to @count bytes.
+ *
+ * Locking: The routine gets called under an irqsave() spinlock; and
+ * the routine locks the struct hvc_iucv_private->lock to call
+ * helper functions.
+ */
+static int hvc_iucv_get_chars(uint32_t vtermno, char *buf, int count)
+{
+ struct hvc_iucv_private *priv = hvc_iucv_get_private(vtermno);
+ int written;
+ int has_more_data;
+
+ if (count <= 0)
+ return 0;
+
+ if (!priv)
+ return -ENODEV;
+
+ spin_lock(&priv->lock);
+ has_more_data = 0;
+ written = hvc_iucv_write(priv, buf, count, &has_more_data);
+ spin_unlock(&priv->lock);
+
+ /* if there are still messages on the queue... schedule another run */
+ if (has_more_data)
+ hvc_kick();
+
+ return written;
+}
+
+/**
+ * hvc_iucv_queue() - Buffer terminal data for sending.
+ * @priv: Pointer to struct hvc_iucv_private instance.
+ * @buf: Buffer containing data to send.
+ * @count: Size of buffer and amount of data to send.
+ *
+ * The function queues data for sending. To actually send the buffered data,
+ * a work queue function is scheduled (with QUEUE_SNDBUF_DELAY).
+ * The function returns the number of data bytes that has been buffered.
+ *
+ * If the device is not connected, data is ignored and the function returns
+ * @count.
+ * If the buffer is full, the function returns 0.
+ * If an existing IUCV communicaton path has been severed, -EPIPE is returned
+ * (that can be passed to HVC layer to cause a tty hangup).
+ */
+static int hvc_iucv_queue(struct hvc_iucv_private *priv, const char *buf,
+ int count)
+{
+ size_t len;
+
+ if (priv->iucv_state == IUCV_DISCONN)
+ return count; /* ignore data */
+
+ if (priv->iucv_state == IUCV_SEVERED)
+ return -EPIPE;
+
+ len = min_t(size_t, count, SNDBUF_SIZE - priv->sndbuf_len);
+ if (!len)
+ return 0;
+
+ memcpy(priv->sndbuf + priv->sndbuf_len, buf, len);
+ priv->sndbuf_len += len;
+
+ if (priv->iucv_state == IUCV_CONNECTED)
+ schedule_delayed_work(&priv->sndbuf_work, QUEUE_SNDBUF_DELAY);
+
+ return len;
+}
+
+/**
+ * hvc_iucv_send() - Send an IUCV message containing terminal data.
+ * @priv: Pointer to struct hvc_iucv_private instance.
+ *
+ * If an IUCV communication path has been established, the buffered output data
+ * is sent via an IUCV message and the number of bytes sent is returned.
+ * Returns 0 if there is no established IUCV communication path or
+ * -EPIPE if an existing IUCV communicaton path has been severed.
+ */
+static int hvc_iucv_send(struct hvc_iucv_private *priv)
+{
+ struct iucv_tty_buffer *sb;
+ int rc, len;
+
+ if (priv->iucv_state == IUCV_SEVERED)
+ return -EPIPE;
+
+ if (priv->iucv_state == IUCV_DISCONN)
+ return -EIO;
+
+ if (!priv->sndbuf_len)
+ return 0;
+
+ /* allocate internal buffer to store msg data and also compute total
+ * message length */
+ sb = alloc_tty_buffer(priv->sndbuf_len, GFP_ATOMIC);
+ if (!sb)
+ return -ENOMEM;
+
+ memcpy(sb->mbuf->data, priv->sndbuf, priv->sndbuf_len);
+ sb->mbuf->datalen = (u16) priv->sndbuf_len;
+ sb->msg.length = MSG_SIZE(sb->mbuf->datalen);
+
+ list_add_tail(&sb->list, &priv->tty_outqueue);
+
+ rc = __iucv_message_send(priv->path, &sb->msg, 0, 0,
+ (void *) sb->mbuf, sb->msg.length);
+ if (rc) {
+ /* drop the message here; however we might want to handle
+ * 0x03 (msg limit reached) by trying again... */
+ list_del(&sb->list);
+ destroy_tty_buffer(sb);
+ }
+ len = priv->sndbuf_len;
+ priv->sndbuf_len = 0;
+
+ return len;
+}
+
+/**
+ * hvc_iucv_sndbuf_work() - Send buffered data over IUCV
+ * @work: Work structure.
+ *
+ * This work queue function sends buffered output data over IUCV and,
+ * if not all buffered data could be sent, reschedules itself.
+ */
+static void hvc_iucv_sndbuf_work(struct work_struct *work)
+{
+ struct hvc_iucv_private *priv;
+
+ priv = container_of(work, struct hvc_iucv_private, sndbuf_work.work);
+ if (!priv)
+ return;
+
+ spin_lock_bh(&priv->lock);
+ hvc_iucv_send(priv);
+ spin_unlock_bh(&priv->lock);
+}
+
+/**
+ * hvc_iucv_put_chars() - HVC put_chars operation.
+ * @vtermno: HVC virtual terminal number.
+ * @buf: Pointer to an buffer to read data from
+ * @count: Size of buffer available for reading
+ *
+ * The HVC thread calls this method to write characters to the back-end.
+ * The function calls hvc_iucv_queue() to queue terminal data for sending.
+ *
+ * Locking: The method gets called under an irqsave() spinlock; and
+ * locks struct hvc_iucv_private->lock.
+ */
+static int hvc_iucv_put_chars(uint32_t vtermno, const char *buf, int count)
+{
+ struct hvc_iucv_private *priv = hvc_iucv_get_private(vtermno);
+ int queued;
+
+ if (count <= 0)
+ return 0;
+
+ if (!priv)
+ return -ENODEV;
+
+ spin_lock(&priv->lock);
+ queued = hvc_iucv_queue(priv, buf, count);
+ spin_unlock(&priv->lock);
+
+ return queued;
+}
+
+/**
+ * hvc_iucv_notifier_add() - HVC notifier for opening a TTY for the first time.
+ * @hp: Pointer to the HVC device (struct hvc_struct)
+ * @id: Additional data (originally passed to hvc_alloc): the index of an struct
+ * hvc_iucv_private instance.
+ *
+ * The function sets the tty state to TTY_OPENED for the struct hvc_iucv_private
+ * instance that is derived from @id. Always returns 0.
+ *
+ * Locking: struct hvc_iucv_private->lock, spin_lock_bh
+ */
+static int hvc_iucv_notifier_add(struct hvc_struct *hp, int id)
+{
+ struct hvc_iucv_private *priv;
+
+ priv = hvc_iucv_get_private(id);
+ if (!priv)
+ return 0;
+
+ spin_lock_bh(&priv->lock);
+ priv->tty_state = TTY_OPENED;
+ spin_unlock_bh(&priv->lock);
+
+ return 0;
+}
+
+/**
+ * hvc_iucv_cleanup() - Clean up and reset a z/VM IUCV HVC instance.
+ * @priv: Pointer to the struct hvc_iucv_private instance.
+ */
+static void hvc_iucv_cleanup(struct hvc_iucv_private *priv)
+{
+ destroy_tty_buffer_list(&priv->tty_outqueue);
+ destroy_tty_buffer_list(&priv->tty_inqueue);
+
+ priv->tty_state = TTY_CLOSED;
+ priv->iucv_state = IUCV_DISCONN;
+
+ priv->sndbuf_len = 0;
+}
+
+/**
+ * tty_outqueue_empty() - Test if the tty outq is empty
+ * @priv: Pointer to struct hvc_iucv_private instance.
+ */
+static inline int tty_outqueue_empty(struct hvc_iucv_private *priv)
+{
+ int rc;
+
+ spin_lock_bh(&priv->lock);
+ rc = list_empty(&priv->tty_outqueue);
+ spin_unlock_bh(&priv->lock);
+
+ return rc;
+}
+
+/**
+ * flush_sndbuf_sync() - Flush send buffer and wait for completion
+ * @priv: Pointer to struct hvc_iucv_private instance.
+ *
+ * The routine cancels a pending sndbuf work, calls hvc_iucv_send()
+ * to flush any buffered terminal output data and waits for completion.
+ */
+static void flush_sndbuf_sync(struct hvc_iucv_private *priv)
+{
+ int sync_wait;
+
+ cancel_delayed_work_sync(&priv->sndbuf_work);
+
+ spin_lock_bh(&priv->lock);
+ hvc_iucv_send(priv); /* force sending buffered data */
+ sync_wait = !list_empty(&priv->tty_outqueue); /* anything queued ? */
+ spin_unlock_bh(&priv->lock);
+
+ if (sync_wait)
+ wait_event_timeout(priv->sndbuf_waitq,
+ tty_outqueue_empty(priv), HZ/10);
+}
+
+/**
+ * hvc_iucv_hangup() - Sever IUCV path and schedule hvc tty hang up
+ * @priv: Pointer to hvc_iucv_private structure
+ *
+ * This routine severs an existing IUCV communication path and hangs
+ * up the underlying HVC terminal device.
+ * The hang-up occurs only if an IUCV communication path is established;
+ * otherwise there is no need to hang up the terminal device.
+ *
+ * The IUCV HVC hang-up is separated into two steps:
+ * 1. After the IUCV path has been severed, the iucv_state is set to
+ * IUCV_SEVERED.
+ * 2. Later, when the HVC thread calls hvc_iucv_get_chars(), the
+ * IUCV_SEVERED state causes the tty hang-up in the HVC layer.
+ *
+ * If the tty has not yet been opened, clean up the hvc_iucv_private
+ * structure to allow re-connects.
+ * If the tty has been opened, let get_chars() return -EPIPE to signal
+ * the HVC layer to hang up the tty and, if so, wake up the HVC thread
+ * to call get_chars()...
+ *
+ * Special notes on hanging up a HVC terminal instantiated as console:
+ * Hang-up: 1. do_tty_hangup() replaces file ops (= hung_up_tty_fops)
+ * 2. do_tty_hangup() calls tty->ops->close() for console_filp
+ * => no hangup notifier is called by HVC (default)
+ * 2. hvc_close() returns because of tty_hung_up_p(filp)
+ * => no delete notifier is called!
+ * Finally, the back-end is not being notified, thus, the tty session is
+ * kept active (TTY_OPEN) to be ready for re-connects.
+ *
+ * Locking: spin_lock(&priv->lock) w/o disabling bh
+ */
+static void hvc_iucv_hangup(struct hvc_iucv_private *priv)
+{
+ struct iucv_path *path;
+
+ path = NULL;
+ spin_lock(&priv->lock);
+ if (priv->iucv_state == IUCV_CONNECTED) {
+ path = priv->path;
+ priv->path = NULL;
+ priv->iucv_state = IUCV_SEVERED;
+ if (priv->tty_state == TTY_CLOSED)
+ hvc_iucv_cleanup(priv);
+ else
+ /* console is special (see above) */
+ if (priv->is_console) {
+ hvc_iucv_cleanup(priv);
+ priv->tty_state = TTY_OPENED;
+ } else
+ hvc_kick();
+ }
+ spin_unlock(&priv->lock);
+
+ /* finally sever path (outside of priv->lock due to lock ordering) */
+ if (path) {
+ iucv_path_sever(path, NULL);
+ iucv_path_free(path);
+ }
+}
+
+/**
+ * hvc_iucv_notifier_hangup() - HVC notifier for TTY hangups.
+ * @hp: Pointer to the HVC device (struct hvc_struct)
+ * @id: Additional data (originally passed to hvc_alloc):
+ * the index of an struct hvc_iucv_private instance.
+ *
+ * This routine notifies the HVC back-end that a tty hangup (carrier loss,
+ * virtual or otherwise) has occurred.
+ * The z/VM IUCV HVC device driver ignores virtual hangups (vhangup())
+ * to keep an existing IUCV communication path established.
+ * (Background: vhangup() is called from user space (by getty or login) to
+ * disable writing to the tty by other applications).
+ * If the tty has been opened and an established IUCV path has been severed
+ * (we caused the tty hangup), the function calls hvc_iucv_cleanup().
+ *
+ * Locking: struct hvc_iucv_private->lock
+ */
+static void hvc_iucv_notifier_hangup(struct hvc_struct *hp, int id)
+{
+ struct hvc_iucv_private *priv;
+
+ priv = hvc_iucv_get_private(id);
+ if (!priv)
+ return;
+
+ flush_sndbuf_sync(priv);
+
+ spin_lock_bh(&priv->lock);
+ /* NOTE: If the hangup was scheduled by ourself (from the iucv
+ * path_servered callback [IUCV_SEVERED]), we have to clean up
+ * our structure and to set state to TTY_CLOSED.
+ * If the tty was hung up otherwise (e.g. vhangup()), then we
+ * ignore this hangup and keep an established IUCV path open...
+ * (...the reason is that we are not able to connect back to the
+ * client if we disconnect on hang up) */
+ priv->tty_state = TTY_CLOSED;
+
+ if (priv->iucv_state == IUCV_SEVERED)
+ hvc_iucv_cleanup(priv);
+ spin_unlock_bh(&priv->lock);
+}
+
+/**
+ * hvc_iucv_notifier_del() - HVC notifier for closing a TTY for the last time.
+ * @hp: Pointer to the HVC device (struct hvc_struct)
+ * @id: Additional data (originally passed to hvc_alloc):
+ * the index of an struct hvc_iucv_private instance.
+ *
+ * This routine notifies the HVC back-end that the last tty device fd has been
+ * closed. The function calls hvc_iucv_cleanup() to clean up the struct
+ * hvc_iucv_private instance.
+ *
+ * Locking: struct hvc_iucv_private->lock
+ */
+static void hvc_iucv_notifier_del(struct hvc_struct *hp, int id)
+{
+ struct hvc_iucv_private *priv;
+ struct iucv_path *path;
+
+ priv = hvc_iucv_get_private(id);
+ if (!priv)
+ return;
+
+ flush_sndbuf_sync(priv);
+
+ spin_lock_bh(&priv->lock);
+ path = priv->path; /* save reference to IUCV path */
+ priv->path = NULL;
+ hvc_iucv_cleanup(priv);
+ spin_unlock_bh(&priv->lock);
+
+ /* sever IUCV path outside of priv->lock due to lock ordering of:
+ * priv->lock <--> iucv_table_lock */
+ if (path) {
+ iucv_path_sever(path, NULL);
+ iucv_path_free(path);
+ }
+}
+
+/**
+ * hvc_iucv_filter_connreq() - Filter connection request based on z/VM user ID
+ * @ipvmid: Originating z/VM user ID (right padded with blanks)
+ *
+ * Returns 0 if the z/VM user ID @ipvmid is allowed to connection, otherwise
+ * non-zero.
+ */
+static int hvc_iucv_filter_connreq(u8 ipvmid[8])
+{
+ size_t i;
+
+ /* Note: default policy is ACCEPT if no filter is set */
+ if (!hvc_iucv_filter_size)
+ return 0;
+
+ for (i = 0; i < hvc_iucv_filter_size; i++)
+ if (0 == memcmp(ipvmid, hvc_iucv_filter + (8 * i), 8))
+ return 0;
+ return 1;
+}
+
+/**
+ * hvc_iucv_path_pending() - IUCV handler to process a connection request.
+ * @path: Pending path (struct iucv_path)
+ * @ipvmid: z/VM system identifier of originator
+ * @ipuser: User specified data for this path
+ * (AF_IUCV: port/service name and originator port)
+ *
+ * The function uses the @ipuser data to determine if the pending path belongs
+ * to a terminal managed by this device driver.
+ * If the path belongs to this driver, ensure that the terminal is not accessed
+ * multiple times (only one connection to a terminal is allowed).
+ * If the terminal is not yet connected, the pending path is accepted and is
+ * associated to the appropriate struct hvc_iucv_private instance.
+ *
+ * Returns 0 if @path belongs to a terminal managed by the this device driver;
+ * otherwise returns -ENODEV in order to dispatch this path to other handlers.
+ *
+ * Locking: struct hvc_iucv_private->lock
+ */
+static int hvc_iucv_path_pending(struct iucv_path *path,
+ u8 ipvmid[8], u8 ipuser[16])
+{
+ struct hvc_iucv_private *priv;
+ u8 nuser_data[16];
+ u8 vm_user_id[9];
+ int i, rc;
+
+ priv = NULL;
+ for (i = 0; i < hvc_iucv_devices; i++)
+ if (hvc_iucv_table[i] &&
+ (0 == memcmp(hvc_iucv_table[i]->srv_name, ipuser, 8))) {
+ priv = hvc_iucv_table[i];
+ break;
+ }
+ if (!priv)
+ return -ENODEV;
+
+ /* Enforce that ipvmid is allowed to connect to us */
+ read_lock(&hvc_iucv_filter_lock);
+ rc = hvc_iucv_filter_connreq(ipvmid);
+ read_unlock(&hvc_iucv_filter_lock);
+ if (rc) {
+ iucv_path_sever(path, ipuser);
+ iucv_path_free(path);
+ memcpy(vm_user_id, ipvmid, 8);
+ vm_user_id[8] = 0;
+ pr_info("A connection request from z/VM user ID %s "
+ "was refused\n", vm_user_id);
+ return 0;
+ }
+
+ spin_lock(&priv->lock);
+
+ /* If the terminal is already connected or being severed, then sever
+ * this path to enforce that there is only ONE established communication
+ * path per terminal. */
+ if (priv->iucv_state != IUCV_DISCONN) {
+ iucv_path_sever(path, ipuser);
+ iucv_path_free(path);
+ goto out_path_handled;
+ }
+
+ /* accept path */
+ memcpy(nuser_data, ipuser + 8, 8); /* remote service (for af_iucv) */
+ memcpy(nuser_data + 8, ipuser, 8); /* local service (for af_iucv) */
+ path->msglim = 0xffff; /* IUCV MSGLIMIT */
+ path->flags &= ~IUCV_IPRMDATA; /* TODO: use IUCV_IPRMDATA */
+ rc = iucv_path_accept(path, &hvc_iucv_handler, nuser_data, priv);
+ if (rc) {
+ iucv_path_sever(path, ipuser);
+ iucv_path_free(path);
+ goto out_path_handled;
+ }
+ priv->path = path;
+ priv->iucv_state = IUCV_CONNECTED;
+
+ /* flush buffered output data... */
+ schedule_delayed_work(&priv->sndbuf_work, 5);
+
+out_path_handled:
+ spin_unlock(&priv->lock);
+ return 0;
+}
+
+/**
+ * hvc_iucv_path_severed() - IUCV handler to process a path sever.
+ * @path: Pending path (struct iucv_path)
+ * @ipuser: User specified data for this path
+ * (AF_IUCV: port/service name and originator port)
+ *
+ * This function calls the hvc_iucv_hangup() function for the
+ * respective IUCV HVC terminal.
+ *
+ * Locking: struct hvc_iucv_private->lock
+ */
+static void hvc_iucv_path_severed(struct iucv_path *path, u8 ipuser[16])
+{
+ struct hvc_iucv_private *priv = path->private;
+
+ hvc_iucv_hangup(priv);
+}
+
+/**
+ * hvc_iucv_msg_pending() - IUCV handler to process an incoming IUCV message.
+ * @path: Pending path (struct iucv_path)
+ * @msg: Pointer to the IUCV message
+ *
+ * The function puts an incoming message on the input queue for later
+ * processing (by hvc_iucv_get_chars() / hvc_iucv_write()).
+ * If the tty has not yet been opened, the message is rejected.
+ *
+ * Locking: struct hvc_iucv_private->lock
+ */
+static void hvc_iucv_msg_pending(struct iucv_path *path,
+ struct iucv_message *msg)
+{
+ struct hvc_iucv_private *priv = path->private;
+ struct iucv_tty_buffer *rb;
+
+ /* reject messages that exceed max size of iucv_tty_msg->datalen */
+ if (msg->length > MSG_SIZE(MSG_MAX_DATALEN)) {
+ iucv_message_reject(path, msg);
+ return;
+ }
+
+ spin_lock(&priv->lock);
+
+ /* reject messages if tty has not yet been opened */
+ if (priv->tty_state == TTY_CLOSED) {
+ iucv_message_reject(path, msg);
+ goto unlock_return;
+ }
+
+ /* allocate tty buffer to save iucv msg only */
+ rb = alloc_tty_buffer(0, GFP_ATOMIC);
+ if (!rb) {
+ iucv_message_reject(path, msg);
+ goto unlock_return; /* -ENOMEM */
+ }
+ rb->msg = *msg;
+
+ list_add_tail(&rb->list, &priv->tty_inqueue);
+
+ hvc_kick(); /* wake up hvc thread */
+
+unlock_return:
+ spin_unlock(&priv->lock);
+}
+
+/**
+ * hvc_iucv_msg_complete() - IUCV handler to process message completion
+ * @path: Pending path (struct iucv_path)
+ * @msg: Pointer to the IUCV message
+ *
+ * The function is called upon completion of message delivery to remove the
+ * message from the outqueue. Additional delivery information can be found
+ * msg->audit: rejected messages (0x040000 (IPADRJCT)), and
+ * purged messages (0x010000 (IPADPGNR)).
+ *
+ * Locking: struct hvc_iucv_private->lock
+ */
+static void hvc_iucv_msg_complete(struct iucv_path *path,
+ struct iucv_message *msg)
+{
+ struct hvc_iucv_private *priv = path->private;
+ struct iucv_tty_buffer *ent, *next;
+ LIST_HEAD(list_remove);
+
+ spin_lock(&priv->lock);
+ list_for_each_entry_safe(ent, next, &priv->tty_outqueue, list)
+ if (ent->msg.id == msg->id) {
+ list_move(&ent->list, &list_remove);
+ break;
+ }
+ wake_up(&priv->sndbuf_waitq);
+ spin_unlock(&priv->lock);
+ destroy_tty_buffer_list(&list_remove);
+}
+
+/**
+ * hvc_iucv_pm_freeze() - Freeze PM callback
+ * @dev: IUVC HVC terminal device
+ *
+ * Sever an established IUCV communication path and
+ * trigger a hang-up of the underlying HVC terminal.
+ */
+static int hvc_iucv_pm_freeze(struct device *dev)
+{
+ struct hvc_iucv_private *priv = dev_get_drvdata(dev);
+
+ local_bh_disable();
+ hvc_iucv_hangup(priv);
+ local_bh_enable();
+
+ return 0;
+}
+
+/**
+ * hvc_iucv_pm_restore_thaw() - Thaw and restore PM callback
+ * @dev: IUVC HVC terminal device
+ *
+ * Wake up the HVC thread to trigger hang-up and respective
+ * HVC back-end notifier invocations.
+ */
+static int hvc_iucv_pm_restore_thaw(struct device *dev)
+{
+ hvc_kick();
+ return 0;
+}
+
+
+/* HVC operations */
+static const struct hv_ops hvc_iucv_ops = {
+ .get_chars = hvc_iucv_get_chars,
+ .put_chars = hvc_iucv_put_chars,
+ .notifier_add = hvc_iucv_notifier_add,
+ .notifier_del = hvc_iucv_notifier_del,
+ .notifier_hangup = hvc_iucv_notifier_hangup,
+};
+
+/* Suspend / resume device operations */
+static const struct dev_pm_ops hvc_iucv_pm_ops = {
+ .freeze = hvc_iucv_pm_freeze,
+ .thaw = hvc_iucv_pm_restore_thaw,
+ .restore = hvc_iucv_pm_restore_thaw,
+};
+
+/* IUCV HVC device driver */
+static struct device_driver hvc_iucv_driver = {
+ .name = KMSG_COMPONENT,
+ .bus = &iucv_bus,
+ .pm = &hvc_iucv_pm_ops,
+};
+
+/**
+ * hvc_iucv_alloc() - Allocates a new struct hvc_iucv_private instance
+ * @id: hvc_iucv_table index
+ * @is_console: Flag if the instance is used as Linux console
+ *
+ * This function allocates a new hvc_iucv_private structure and stores
+ * the instance in hvc_iucv_table at index @id.
+ * Returns 0 on success; otherwise non-zero.
+ */
+static int __init hvc_iucv_alloc(int id, unsigned int is_console)
+{
+ struct hvc_iucv_private *priv;
+ char name[9];
+ int rc;
+
+ priv = kzalloc(sizeof(struct hvc_iucv_private), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ INIT_LIST_HEAD(&priv->tty_outqueue);
+ INIT_LIST_HEAD(&priv->tty_inqueue);
+ INIT_DELAYED_WORK(&priv->sndbuf_work, hvc_iucv_sndbuf_work);
+ init_waitqueue_head(&priv->sndbuf_waitq);
+
+ priv->sndbuf = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!priv->sndbuf) {
+ kfree(priv);
+ return -ENOMEM;
+ }
+
+ /* set console flag */
+ priv->is_console = is_console;
+
+ /* allocate hvc device */
+ priv->hvc = hvc_alloc(HVC_IUCV_MAGIC + id, /* PAGE_SIZE */
+ HVC_IUCV_MAGIC + id, &hvc_iucv_ops, 256);
+ if (IS_ERR(priv->hvc)) {
+ rc = PTR_ERR(priv->hvc);
+ goto out_error_hvc;
+ }
+
+ /* notify HVC thread instead of using polling */
+ priv->hvc->irq_requested = 1;
+
+ /* setup iucv related information */
+ snprintf(name, 9, "lnxhvc%-2d", id);
+ memcpy(priv->srv_name, name, 8);
+ ASCEBC(priv->srv_name, 8);
+
+ /* create and setup device */
+ priv->dev = kzalloc(sizeof(*priv->dev), GFP_KERNEL);
+ if (!priv->dev) {
+ rc = -ENOMEM;
+ goto out_error_dev;
+ }
+ dev_set_name(priv->dev, "hvc_iucv%d", id);
+ dev_set_drvdata(priv->dev, priv);
+ priv->dev->bus = &iucv_bus;
+ priv->dev->parent = iucv_root;
+ priv->dev->driver = &hvc_iucv_driver;
+ priv->dev->release = (void (*)(struct device *)) kfree;
+ rc = device_register(priv->dev);
+ if (rc) {
+ put_device(priv->dev);
+ goto out_error_dev;
+ }
+
+ hvc_iucv_table[id] = priv;
+ return 0;
+
+out_error_dev:
+ hvc_remove(priv->hvc);
+out_error_hvc:
+ free_page((unsigned long) priv->sndbuf);
+ kfree(priv);
+
+ return rc;
+}
+
+/**
+ * hvc_iucv_destroy() - Destroy and free hvc_iucv_private instances
+ */
+static void __init hvc_iucv_destroy(struct hvc_iucv_private *priv)
+{
+ hvc_remove(priv->hvc);
+ device_unregister(priv->dev);
+ free_page((unsigned long) priv->sndbuf);
+ kfree(priv);
+}
+
+/**
+ * hvc_iucv_parse_filter() - Parse filter for a single z/VM user ID
+ * @filter: String containing a comma-separated list of z/VM user IDs
+ */
+static const char *hvc_iucv_parse_filter(const char *filter, char *dest)
+{
+ const char *nextdelim, *residual;
+ size_t len;
+
+ nextdelim = strchr(filter, ',');
+ if (nextdelim) {
+ len = nextdelim - filter;
+ residual = nextdelim + 1;
+ } else {
+ len = strlen(filter);
+ residual = filter + len;
+ }
+
+ if (len == 0)
+ return ERR_PTR(-EINVAL);
+
+ /* check for '\n' (if called from sysfs) */
+ if (filter[len - 1] == '\n')
+ len--;
+
+ if (len > 8)
+ return ERR_PTR(-EINVAL);
+
+ /* pad with blanks and save upper case version of user ID */
+ memset(dest, ' ', 8);
+ while (len--)
+ dest[len] = toupper(filter[len]);
+ return residual;
+}
+
+/**
+ * hvc_iucv_setup_filter() - Set up z/VM user ID filter
+ * @filter: String consisting of a comma-separated list of z/VM user IDs
+ *
+ * The function parses the @filter string and creates an array containing
+ * the list of z/VM user ID filter entries.
+ * Return code 0 means success, -EINVAL if the filter is syntactically
+ * incorrect, -ENOMEM if there was not enough memory to allocate the
+ * filter list array, or -ENOSPC if too many z/VM user IDs have been specified.
+ */
+static int hvc_iucv_setup_filter(const char *val)
+{
+ const char *residual;
+ int err;
+ size_t size, count;
+ void *array, *old_filter;
+
+ count = strlen(val);
+ if (count == 0 || (count == 1 && val[0] == '\n')) {
+ size = 0;
+ array = NULL;
+ goto out_replace_filter; /* clear filter */
+ }
+
+ /* count user IDs in order to allocate sufficient memory */
+ size = 1;
+ residual = val;
+ while ((residual = strchr(residual, ',')) != NULL) {
+ residual++;
+ size++;
+ }
+
+ /* check if the specified list exceeds the filter limit */
+ if (size > MAX_VMID_FILTER)
+ return -ENOSPC;
+
+ array = kzalloc(size * 8, GFP_KERNEL);
+ if (!array)
+ return -ENOMEM;
+
+ count = size;
+ residual = val;
+ while (*residual && count) {
+ residual = hvc_iucv_parse_filter(residual,
+ array + ((size - count) * 8));
+ if (IS_ERR(residual)) {
+ err = PTR_ERR(residual);
+ kfree(array);
+ goto out_err;
+ }
+ count--;
+ }
+
+out_replace_filter:
+ write_lock_bh(&hvc_iucv_filter_lock);
+ old_filter = hvc_iucv_filter;
+ hvc_iucv_filter_size = size;
+ hvc_iucv_filter = array;
+ write_unlock_bh(&hvc_iucv_filter_lock);
+ kfree(old_filter);
+
+ err = 0;
+out_err:
+ return err;
+}
+
+/**
+ * param_set_vmidfilter() - Set z/VM user ID filter parameter
+ * @val: String consisting of a comma-separated list of z/VM user IDs
+ * @kp: Kernel parameter pointing to hvc_iucv_filter array
+ *
+ * The function sets up the z/VM user ID filter specified as comma-separated
+ * list of user IDs in @val.
+ * Note: If it is called early in the boot process, @val is stored and
+ * parsed later in hvc_iucv_init().
+ */
+static int param_set_vmidfilter(const char *val, const struct kernel_param *kp)
+{
+ int rc;
+
+ if (!MACHINE_IS_VM || !hvc_iucv_devices)
+ return -ENODEV;
+
+ if (!val)
+ return -EINVAL;
+
+ rc = 0;
+ if (slab_is_available())
+ rc = hvc_iucv_setup_filter(val);
+ else
+ hvc_iucv_filter_string = val; /* defer... */
+ return rc;
+}
+
+/**
+ * param_get_vmidfilter() - Get z/VM user ID filter
+ * @buffer: Buffer to store z/VM user ID filter,
+ * (buffer size assumption PAGE_SIZE)
+ * @kp: Kernel parameter pointing to the hvc_iucv_filter array
+ *
+ * The function stores the filter as a comma-separated list of z/VM user IDs
+ * in @buffer. Typically, sysfs routines call this function for attr show.
+ */
+static int param_get_vmidfilter(char *buffer, const struct kernel_param *kp)
+{
+ int rc;
+ size_t index, len;
+ void *start, *end;
+
+ if (!MACHINE_IS_VM || !hvc_iucv_devices)
+ return -ENODEV;
+
+ rc = 0;
+ read_lock_bh(&hvc_iucv_filter_lock);
+ for (index = 0; index < hvc_iucv_filter_size; index++) {
+ start = hvc_iucv_filter + (8 * index);
+ end = memchr(start, ' ', 8);
+ len = (end) ? end - start : 8;
+ memcpy(buffer + rc, start, len);
+ rc += len;
+ buffer[rc++] = ',';
+ }
+ read_unlock_bh(&hvc_iucv_filter_lock);
+ if (rc)
+ buffer[--rc] = '\0'; /* replace last comma and update rc */
+ return rc;
+}
+
+#define param_check_vmidfilter(name, p) __param_check(name, p, void)
+
+static struct kernel_param_ops param_ops_vmidfilter = {
+ .set = param_set_vmidfilter,
+ .get = param_get_vmidfilter,
+};
+
+/**
+ * hvc_iucv_init() - z/VM IUCV HVC device driver initialization
+ */
+static int __init hvc_iucv_init(void)
+{
+ int rc;
+ unsigned int i;
+
+ if (!hvc_iucv_devices)
+ return -ENODEV;
+
+ if (!MACHINE_IS_VM) {
+ pr_notice("The z/VM IUCV HVC device driver cannot "
+ "be used without z/VM\n");
+ rc = -ENODEV;
+ goto out_error;
+ }
+
+ if (hvc_iucv_devices > MAX_HVC_IUCV_LINES) {
+ pr_err("%lu is not a valid value for the hvc_iucv= "
+ "kernel parameter\n", hvc_iucv_devices);
+ rc = -EINVAL;
+ goto out_error;
+ }
+
+ /* register IUCV HVC device driver */
+ rc = driver_register(&hvc_iucv_driver);
+ if (rc)
+ goto out_error;
+
+ /* parse hvc_iucv_allow string and create z/VM user ID filter list */
+ if (hvc_iucv_filter_string) {
+ rc = hvc_iucv_setup_filter(hvc_iucv_filter_string);
+ switch (rc) {
+ case 0:
+ break;
+ case -ENOMEM:
+ pr_err("Allocating memory failed with "
+ "reason code=%d\n", 3);
+ goto out_error;
+ case -EINVAL:
+ pr_err("hvc_iucv_allow= does not specify a valid "
+ "z/VM user ID list\n");
+ goto out_error;
+ case -ENOSPC:
+ pr_err("hvc_iucv_allow= specifies too many "
+ "z/VM user IDs\n");
+ goto out_error;
+ default:
+ goto out_error;
+ }
+ }
+
+ hvc_iucv_buffer_cache = kmem_cache_create(KMSG_COMPONENT,
+ sizeof(struct iucv_tty_buffer),
+ 0, 0, NULL);
+ if (!hvc_iucv_buffer_cache) {
+ pr_err("Allocating memory failed with reason code=%d\n", 1);
+ rc = -ENOMEM;
+ goto out_error;
+ }
+
+ hvc_iucv_mempool = mempool_create_slab_pool(MEMPOOL_MIN_NR,
+ hvc_iucv_buffer_cache);
+ if (!hvc_iucv_mempool) {
+ pr_err("Allocating memory failed with reason code=%d\n", 2);
+ kmem_cache_destroy(hvc_iucv_buffer_cache);
+ rc = -ENOMEM;
+ goto out_error;
+ }
+
+ /* register the first terminal device as console
+ * (must be done before allocating hvc terminal devices) */
+ rc = hvc_instantiate(HVC_IUCV_MAGIC, IUCV_HVC_CON_IDX, &hvc_iucv_ops);
+ if (rc) {
+ pr_err("Registering HVC terminal device as "
+ "Linux console failed\n");
+ goto out_error_memory;
+ }
+
+ /* allocate hvc_iucv_private structs */
+ for (i = 0; i < hvc_iucv_devices; i++) {
+ rc = hvc_iucv_alloc(i, (i == IUCV_HVC_CON_IDX) ? 1 : 0);
+ if (rc) {
+ pr_err("Creating a new HVC terminal device "
+ "failed with error code=%d\n", rc);
+ goto out_error_hvc;
+ }
+ }
+
+ /* register IUCV callback handler */
+ rc = iucv_register(&hvc_iucv_handler, 0);
+ if (rc) {
+ pr_err("Registering IUCV handlers failed with error code=%d\n",
+ rc);
+ goto out_error_hvc;
+ }
+
+ return 0;
+
+out_error_hvc:
+ for (i = 0; i < hvc_iucv_devices; i++)
+ if (hvc_iucv_table[i])
+ hvc_iucv_destroy(hvc_iucv_table[i]);
+out_error_memory:
+ mempool_destroy(hvc_iucv_mempool);
+ kmem_cache_destroy(hvc_iucv_buffer_cache);
+out_error:
+ if (hvc_iucv_filter)
+ kfree(hvc_iucv_filter);
+ hvc_iucv_devices = 0; /* ensure that we do not provide any device */
+ return rc;
+}
+
+/**
+ * hvc_iucv_config() - Parsing of hvc_iucv= kernel command line parameter
+ * @val: Parameter value (numeric)
+ */
+static int __init hvc_iucv_config(char *val)
+{
+ return strict_strtoul(val, 10, &hvc_iucv_devices);
+}
+
+
+device_initcall(hvc_iucv_init);
+__setup("hvc_iucv=", hvc_iucv_config);
+core_param(hvc_iucv_allow, hvc_iucv_filter, vmidfilter, 0640);
diff --git a/drivers/tty/hvc/hvc_opal.c b/drivers/tty/hvc/hvc_opal.c
new file mode 100644
index 00000000000..ced26c8ccd5
--- /dev/null
+++ b/drivers/tty/hvc/hvc_opal.c
@@ -0,0 +1,425 @@
+/*
+ * opal driver interface to hvc_console.c
+ *
+ * Copyright 2011 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
+ *
+ * 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
+ *
+ */
+
+#undef DEBUG
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/console.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/export.h>
+
+#include <asm/hvconsole.h>
+#include <asm/prom.h>
+#include <asm/firmware.h>
+#include <asm/hvsi.h>
+#include <asm/udbg.h>
+#include <asm/opal.h>
+
+#include "hvc_console.h"
+
+static const char hvc_opal_name[] = "hvc_opal";
+
+static struct of_device_id hvc_opal_match[] __devinitdata = {
+ { .name = "serial", .compatible = "ibm,opal-console-raw" },
+ { .name = "serial", .compatible = "ibm,opal-console-hvsi" },
+ { },
+};
+
+typedef enum hv_protocol {
+ HV_PROTOCOL_RAW,
+ HV_PROTOCOL_HVSI
+} hv_protocol_t;
+
+struct hvc_opal_priv {
+ hv_protocol_t proto; /* Raw data or HVSI packets */
+ struct hvsi_priv hvsi; /* HVSI specific data */
+};
+static struct hvc_opal_priv *hvc_opal_privs[MAX_NR_HVC_CONSOLES];
+
+/* For early boot console */
+static struct hvc_opal_priv hvc_opal_boot_priv;
+static u32 hvc_opal_boot_termno;
+
+static const struct hv_ops hvc_opal_raw_ops = {
+ .get_chars = opal_get_chars,
+ .put_chars = opal_put_chars,
+ .notifier_add = notifier_add_irq,
+ .notifier_del = notifier_del_irq,
+ .notifier_hangup = notifier_hangup_irq,
+};
+
+static int hvc_opal_hvsi_get_chars(uint32_t vtermno, char *buf, int count)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[vtermno];
+
+ if (WARN_ON(!pv))
+ return -ENODEV;
+
+ return hvsilib_get_chars(&pv->hvsi, buf, count);
+}
+
+static int hvc_opal_hvsi_put_chars(uint32_t vtermno, const char *buf, int count)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[vtermno];
+
+ if (WARN_ON(!pv))
+ return -ENODEV;
+
+ return hvsilib_put_chars(&pv->hvsi, buf, count);
+}
+
+static int hvc_opal_hvsi_open(struct hvc_struct *hp, int data)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
+ int rc;
+
+ pr_devel("HVSI@%x: do open !\n", hp->vtermno);
+
+ rc = notifier_add_irq(hp, data);
+ if (rc)
+ return rc;
+
+ return hvsilib_open(&pv->hvsi, hp);
+}
+
+static void hvc_opal_hvsi_close(struct hvc_struct *hp, int data)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
+
+ pr_devel("HVSI@%x: do close !\n", hp->vtermno);
+
+ hvsilib_close(&pv->hvsi, hp);
+
+ notifier_del_irq(hp, data);
+}
+
+void hvc_opal_hvsi_hangup(struct hvc_struct *hp, int data)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
+
+ pr_devel("HVSI@%x: do hangup !\n", hp->vtermno);
+
+ hvsilib_close(&pv->hvsi, hp);
+
+ notifier_hangup_irq(hp, data);
+}
+
+static int hvc_opal_hvsi_tiocmget(struct hvc_struct *hp)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
+
+ if (!pv)
+ return -EINVAL;
+ return pv->hvsi.mctrl;
+}
+
+static int hvc_opal_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set,
+ unsigned int clear)
+{
+ struct hvc_opal_priv *pv = hvc_opal_privs[hp->vtermno];
+
+ pr_devel("HVSI@%x: Set modem control, set=%x,clr=%x\n",
+ hp->vtermno, set, clear);
+
+ if (set & TIOCM_DTR)
+ hvsilib_write_mctrl(&pv->hvsi, 1);
+ else if (clear & TIOCM_DTR)
+ hvsilib_write_mctrl(&pv->hvsi, 0);
+
+ return 0;
+}
+
+static const struct hv_ops hvc_opal_hvsi_ops = {
+ .get_chars = hvc_opal_hvsi_get_chars,
+ .put_chars = hvc_opal_hvsi_put_chars,
+ .notifier_add = hvc_opal_hvsi_open,
+ .notifier_del = hvc_opal_hvsi_close,
+ .notifier_hangup = hvc_opal_hvsi_hangup,
+ .tiocmget = hvc_opal_hvsi_tiocmget,
+ .tiocmset = hvc_opal_hvsi_tiocmset,
+};
+
+static int __devinit hvc_opal_probe(struct platform_device *dev)
+{
+ const struct hv_ops *ops;
+ struct hvc_struct *hp;
+ struct hvc_opal_priv *pv;
+ hv_protocol_t proto;
+ unsigned int termno, boot = 0;
+ const __be32 *reg;
+
+ if (of_device_is_compatible(dev->dev.of_node, "ibm,opal-console-raw")) {
+ proto = HV_PROTOCOL_RAW;
+ ops = &hvc_opal_raw_ops;
+ } else if (of_device_is_compatible(dev->dev.of_node,
+ "ibm,opal-console-hvsi")) {
+ proto = HV_PROTOCOL_HVSI;
+ ops = &hvc_opal_hvsi_ops;
+ } else {
+ pr_err("hvc_opal: Unkown protocol for %s\n",
+ dev->dev.of_node->full_name);
+ return -ENXIO;
+ }
+
+ reg = of_get_property(dev->dev.of_node, "reg", NULL);
+ termno = reg ? be32_to_cpup(reg) : 0;
+
+ /* Is it our boot one ? */
+ if (hvc_opal_privs[termno] == &hvc_opal_boot_priv) {
+ pv = hvc_opal_privs[termno];
+ boot = 1;
+ } else if (hvc_opal_privs[termno] == NULL) {
+ pv = kzalloc(sizeof(struct hvc_opal_priv), GFP_KERNEL);
+ if (!pv)
+ return -ENOMEM;
+ pv->proto = proto;
+ hvc_opal_privs[termno] = pv;
+ if (proto == HV_PROTOCOL_HVSI)
+ hvsilib_init(&pv->hvsi, opal_get_chars, opal_put_chars,
+ termno, 0);
+
+ /* Instanciate now to establish a mapping index==vtermno */
+ hvc_instantiate(termno, termno, ops);
+ } else {
+ pr_err("hvc_opal: Device %s has duplicate terminal number #%d\n",
+ dev->dev.of_node->full_name, termno);
+ return -ENXIO;
+ }
+
+ pr_info("hvc%d: %s protocol on %s%s\n", termno,
+ proto == HV_PROTOCOL_RAW ? "raw" : "hvsi",
+ dev->dev.of_node->full_name,
+ boot ? " (boot console)" : "");
+
+ /* We don't do IRQ yet */
+ hp = hvc_alloc(termno, 0, ops, MAX_VIO_PUT_CHARS);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+ dev_set_drvdata(&dev->dev, hp);
+
+ return 0;
+}
+
+static int __devexit hvc_opal_remove(struct platform_device *dev)
+{
+ struct hvc_struct *hp = dev_get_drvdata(&dev->dev);
+ int rc, termno;
+
+ termno = hp->vtermno;
+ rc = hvc_remove(hp);
+ if (rc == 0) {
+ if (hvc_opal_privs[termno] != &hvc_opal_boot_priv)
+ kfree(hvc_opal_privs[termno]);
+ hvc_opal_privs[termno] = NULL;
+ }
+ return rc;
+}
+
+static struct platform_driver hvc_opal_driver = {
+ .probe = hvc_opal_probe,
+ .remove = __devexit_p(hvc_opal_remove),
+ .driver = {
+ .name = hvc_opal_name,
+ .owner = THIS_MODULE,
+ .of_match_table = hvc_opal_match,
+ }
+};
+
+static int __init hvc_opal_init(void)
+{
+ if (!firmware_has_feature(FW_FEATURE_OPAL))
+ return -ENODEV;
+
+ /* Register as a vio device to receive callbacks */
+ return platform_driver_register(&hvc_opal_driver);
+}
+module_init(hvc_opal_init);
+
+static void __exit hvc_opal_exit(void)
+{
+ platform_driver_unregister(&hvc_opal_driver);
+}
+module_exit(hvc_opal_exit);
+
+static void udbg_opal_putc(char c)
+{
+ unsigned int termno = hvc_opal_boot_termno;
+ int count = -1;
+
+ if (c == '\n')
+ udbg_opal_putc('\r');
+
+ do {
+ switch(hvc_opal_boot_priv.proto) {
+ case HV_PROTOCOL_RAW:
+ count = opal_put_chars(termno, &c, 1);
+ break;
+ case HV_PROTOCOL_HVSI:
+ count = hvc_opal_hvsi_put_chars(termno, &c, 1);
+ break;
+ }
+ } while(count == 0 || count == -EAGAIN);
+}
+
+static int udbg_opal_getc_poll(void)
+{
+ unsigned int termno = hvc_opal_boot_termno;
+ int rc = 0;
+ char c;
+
+ switch(hvc_opal_boot_priv.proto) {
+ case HV_PROTOCOL_RAW:
+ rc = opal_get_chars(termno, &c, 1);
+ break;
+ case HV_PROTOCOL_HVSI:
+ rc = hvc_opal_hvsi_get_chars(termno, &c, 1);
+ break;
+ }
+ if (!rc)
+ return -1;
+ return c;
+}
+
+static int udbg_opal_getc(void)
+{
+ int ch;
+ for (;;) {
+ ch = udbg_opal_getc_poll();
+ if (ch == -1) {
+ /* This shouldn't be needed...but... */
+ volatile unsigned long delay;
+ for (delay=0; delay < 2000000; delay++)
+ ;
+ } else {
+ return ch;
+ }
+ }
+}
+
+static void udbg_init_opal_common(void)
+{
+ udbg_putc = udbg_opal_putc;
+ udbg_getc = udbg_opal_getc;
+ udbg_getc_poll = udbg_opal_getc_poll;
+ tb_ticks_per_usec = 0x200; /* Make udelay not suck */
+}
+
+void __init hvc_opal_init_early(void)
+{
+ struct device_node *stdout_node = NULL;
+ const u32 *termno;
+ const char *name = NULL;
+ const struct hv_ops *ops;
+ u32 index;
+
+ /* find the boot console from /chosen/stdout */
+ if (of_chosen)
+ name = of_get_property(of_chosen, "linux,stdout-path", NULL);
+ if (name) {
+ stdout_node = of_find_node_by_path(name);
+ if (!stdout_node) {
+ pr_err("hvc_opal: Failed to locate default console!\n");
+ return;
+ }
+ } else {
+ struct device_node *opal, *np;
+
+ /* Current OPAL takeover doesn't provide the stdout
+ * path, so we hard wire it
+ */
+ opal = of_find_node_by_path("/ibm,opal/consoles");
+ if (opal)
+ pr_devel("hvc_opal: Found consoles in new location\n");
+ if (!opal) {
+ opal = of_find_node_by_path("/ibm,opal");
+ if (opal)
+ pr_devel("hvc_opal: "
+ "Found consoles in old location\n");
+ }
+ if (!opal)
+ return;
+ for_each_child_of_node(opal, np) {
+ if (!strcmp(np->name, "serial")) {
+ stdout_node = np;
+ break;
+ }
+ }
+ of_node_put(opal);
+ }
+ if (!stdout_node)
+ return;
+ termno = of_get_property(stdout_node, "reg", NULL);
+ index = termno ? *termno : 0;
+ if (index >= MAX_NR_HVC_CONSOLES)
+ return;
+ hvc_opal_privs[index] = &hvc_opal_boot_priv;
+
+ /* Check the protocol */
+ if (of_device_is_compatible(stdout_node, "ibm,opal-console-raw")) {
+ hvc_opal_boot_priv.proto = HV_PROTOCOL_RAW;
+ ops = &hvc_opal_raw_ops;
+ pr_devel("hvc_opal: Found RAW console\n");
+ }
+ else if (of_device_is_compatible(stdout_node,"ibm,opal-console-hvsi")) {
+ hvc_opal_boot_priv.proto = HV_PROTOCOL_HVSI;
+ ops = &hvc_opal_hvsi_ops;
+ hvsilib_init(&hvc_opal_boot_priv.hvsi, opal_get_chars,
+ opal_put_chars, index, 1);
+ /* HVSI, perform the handshake now */
+ hvsilib_establish(&hvc_opal_boot_priv.hvsi);
+ pr_devel("hvc_opal: Found HVSI console\n");
+ } else
+ goto out;
+ hvc_opal_boot_termno = index;
+ udbg_init_opal_common();
+ add_preferred_console("hvc", index, NULL);
+ hvc_instantiate(index, index, ops);
+out:
+ of_node_put(stdout_node);
+}
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_OPAL_RAW
+void __init udbg_init_debug_opal(void)
+{
+ u32 index = CONFIG_PPC_EARLY_DEBUG_OPAL_VTERMNO;
+ hvc_opal_privs[index] = &hvc_opal_boot_priv;
+ hvc_opal_boot_priv.proto = HV_PROTOCOL_RAW;
+ hvc_opal_boot_termno = index;
+ udbg_init_opal_common();
+}
+#endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_RAW */
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI
+void __init udbg_init_debug_opal_hvsi(void)
+{
+ u32 index = CONFIG_PPC_EARLY_DEBUG_OPAL_VTERMNO;
+ hvc_opal_privs[index] = &hvc_opal_boot_priv;
+ hvc_opal_boot_termno = index;
+ udbg_init_opal_common();
+ hvsilib_init(&hvc_opal_boot_priv.hvsi, opal_get_chars, opal_put_chars,
+ index, 1);
+ hvsilib_establish(&hvc_opal_boot_priv.hvsi);
+}
+#endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI */
diff --git a/drivers/tty/hvc/hvc_rtas.c b/drivers/tty/hvc/hvc_rtas.c
new file mode 100644
index 00000000000..61c4a61558d
--- /dev/null
+++ b/drivers/tty/hvc/hvc_rtas.c
@@ -0,0 +1,133 @@
+/*
+ * IBM RTAS driver interface to hvc_console.c
+ *
+ * (C) Copyright IBM Corporation 2001-2005
+ * (C) Copyright Red Hat, Inc. 2005
+ *
+ * Author(s): Maximino Augilar <IBM STI Design Center>
+ * : Ryan S. Arnold <rsa@us.ibm.com>
+ * : Utz Bacher <utz.bacher@de.ibm.com>
+ * : David Woodhouse <dwmw2@infradead.org>
+ *
+ * inspired by drivers/char/hvc_console.c
+ * written by Anton Blanchard 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+
+#include <asm/irq.h>
+#include <asm/rtas.h>
+#include "hvc_console.h"
+
+#define hvc_rtas_cookie 0x67781e15
+struct hvc_struct *hvc_rtas_dev;
+
+static int rtascons_put_char_token = RTAS_UNKNOWN_SERVICE;
+static int rtascons_get_char_token = RTAS_UNKNOWN_SERVICE;
+
+static inline int hvc_rtas_write_console(uint32_t vtermno, const char *buf,
+ int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (rtas_call(rtascons_put_char_token, 1, 1, NULL, buf[i]))
+ break;
+ }
+
+ return i;
+}
+
+static int hvc_rtas_read_console(uint32_t vtermno, char *buf, int count)
+{
+ int i, c;
+
+ for (i = 0; i < count; i++) {
+ if (rtas_call(rtascons_get_char_token, 0, 2, &c))
+ break;
+
+ buf[i] = c;
+ }
+
+ return i;
+}
+
+static const struct hv_ops hvc_rtas_get_put_ops = {
+ .get_chars = hvc_rtas_read_console,
+ .put_chars = hvc_rtas_write_console,
+};
+
+static int __init hvc_rtas_init(void)
+{
+ struct hvc_struct *hp;
+
+ if (rtascons_put_char_token == RTAS_UNKNOWN_SERVICE)
+ rtascons_put_char_token = rtas_token("put-term-char");
+ if (rtascons_put_char_token == RTAS_UNKNOWN_SERVICE)
+ return -EIO;
+
+ if (rtascons_get_char_token == RTAS_UNKNOWN_SERVICE)
+ rtascons_get_char_token = rtas_token("get-term-char");
+ if (rtascons_get_char_token == RTAS_UNKNOWN_SERVICE)
+ return -EIO;
+
+ BUG_ON(hvc_rtas_dev);
+
+ /* Allocate an hvc_struct for the console device we instantiated
+ * earlier. Save off hp so that we can return it on exit */
+ hp = hvc_alloc(hvc_rtas_cookie, NO_IRQ, &hvc_rtas_get_put_ops, 16);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+
+ hvc_rtas_dev = hp;
+
+ return 0;
+}
+module_init(hvc_rtas_init);
+
+/* This will tear down the tty portion of the driver */
+static void __exit hvc_rtas_exit(void)
+{
+ /* Really the fun isn't over until the worker thread breaks down and
+ * the tty cleans up */
+ if (hvc_rtas_dev)
+ hvc_remove(hvc_rtas_dev);
+}
+module_exit(hvc_rtas_exit);
+
+/* This will happen prior to module init. There is no tty at this time? */
+static int __init hvc_rtas_console_init(void)
+{
+ rtascons_put_char_token = rtas_token("put-term-char");
+ if (rtascons_put_char_token == RTAS_UNKNOWN_SERVICE)
+ return -EIO;
+
+ rtascons_get_char_token = rtas_token("get-term-char");
+ if (rtascons_get_char_token == RTAS_UNKNOWN_SERVICE)
+ return -EIO;
+
+ hvc_instantiate(hvc_rtas_cookie, 0, &hvc_rtas_get_put_ops);
+ add_preferred_console("hvc", 0, NULL);
+
+ return 0;
+}
+console_initcall(hvc_rtas_console_init);
diff --git a/drivers/tty/hvc/hvc_tile.c b/drivers/tty/hvc/hvc_tile.c
new file mode 100644
index 00000000000..7a84a059547
--- /dev/null
+++ b/drivers/tty/hvc/hvc_tile.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 Tilera Corporation. 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, version 2.
+ *
+ * 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, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for
+ * more details.
+ *
+ * Tilera TILE Processor hypervisor console
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+
+#include <hv/hypervisor.h>
+
+#include "hvc_console.h"
+
+static int hvc_tile_put_chars(uint32_t vt, const char *buf, int count)
+{
+ return hv_console_write((HV_VirtAddr)buf, count);
+}
+
+static int hvc_tile_get_chars(uint32_t vt, char *buf, int count)
+{
+ int i, c;
+
+ for (i = 0; i < count; ++i) {
+ c = hv_console_read_if_ready();
+ if (c < 0)
+ break;
+ buf[i] = c;
+ }
+
+ return i;
+}
+
+static const struct hv_ops hvc_tile_get_put_ops = {
+ .get_chars = hvc_tile_get_chars,
+ .put_chars = hvc_tile_put_chars,
+};
+
+static int __init hvc_tile_console_init(void)
+{
+ extern void disable_early_printk(void);
+ hvc_instantiate(0, 0, &hvc_tile_get_put_ops);
+ add_preferred_console("hvc", 0, NULL);
+ disable_early_printk();
+ return 0;
+}
+console_initcall(hvc_tile_console_init);
+
+static int __init hvc_tile_init(void)
+{
+ struct hvc_struct *s;
+ s = hvc_alloc(0, 0, &hvc_tile_get_put_ops, 128);
+ return IS_ERR(s) ? PTR_ERR(s) : 0;
+}
+device_initcall(hvc_tile_init);
diff --git a/drivers/tty/hvc/hvc_udbg.c b/drivers/tty/hvc/hvc_udbg.c
new file mode 100644
index 00000000000..b0957e61a7b
--- /dev/null
+++ b/drivers/tty/hvc/hvc_udbg.c
@@ -0,0 +1,96 @@
+/*
+ * udbg interface to hvc_console.c
+ *
+ * (C) Copyright David Gibson, IBM Corporation 2008.
+ *
+ * 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/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/irq.h>
+
+#include <asm/udbg.h>
+
+#include "hvc_console.h"
+
+struct hvc_struct *hvc_udbg_dev;
+
+static int hvc_udbg_put(uint32_t vtermno, const char *buf, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ udbg_putc(buf[i]);
+
+ return i;
+}
+
+static int hvc_udbg_get(uint32_t vtermno, char *buf, int count)
+{
+ int i, c;
+
+ if (!udbg_getc_poll)
+ return 0;
+
+ for (i = 0; i < count; i++) {
+ if ((c = udbg_getc_poll()) == -1)
+ break;
+ buf[i] = c;
+ }
+
+ return i;
+}
+
+static const struct hv_ops hvc_udbg_ops = {
+ .get_chars = hvc_udbg_get,
+ .put_chars = hvc_udbg_put,
+};
+
+static int __init hvc_udbg_init(void)
+{
+ struct hvc_struct *hp;
+
+ BUG_ON(hvc_udbg_dev);
+
+ hp = hvc_alloc(0, NO_IRQ, &hvc_udbg_ops, 16);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+
+ hvc_udbg_dev = hp;
+
+ return 0;
+}
+module_init(hvc_udbg_init);
+
+static void __exit hvc_udbg_exit(void)
+{
+ if (hvc_udbg_dev)
+ hvc_remove(hvc_udbg_dev);
+}
+module_exit(hvc_udbg_exit);
+
+static int __init hvc_udbg_console_init(void)
+{
+ hvc_instantiate(0, 0, &hvc_udbg_ops);
+ add_preferred_console("hvc", 0, NULL);
+
+ return 0;
+}
+console_initcall(hvc_udbg_console_init);
diff --git a/drivers/tty/hvc/hvc_vio.c b/drivers/tty/hvc/hvc_vio.c
new file mode 100644
index 00000000000..fc3c3ad6c07
--- /dev/null
+++ b/drivers/tty/hvc/hvc_vio.c
@@ -0,0 +1,486 @@
+/*
+ * vio driver interface to hvc_console.c
+ *
+ * This code was moved here to allow the remaining code to be reused as a
+ * generic polling mode with semi-reliable transport driver core to the
+ * console and tty subsystems.
+ *
+ *
+ * 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
+ *
+ * TODO:
+ *
+ * - handle error in sending hvsi protocol packets
+ * - retry nego on subsequent sends ?
+ */
+
+#undef DEBUG
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/console.h>
+#include <linux/module.h>
+
+#include <asm/hvconsole.h>
+#include <asm/vio.h>
+#include <asm/prom.h>
+#include <asm/firmware.h>
+#include <asm/hvsi.h>
+#include <asm/udbg.h>
+
+#include "hvc_console.h"
+
+static const char hvc_driver_name[] = "hvc_console";
+
+static struct vio_device_id hvc_driver_table[] __devinitdata = {
+ {"serial", "hvterm1"},
+#ifndef HVC_OLD_HVSI
+ {"serial", "hvterm-protocol"},
+#endif
+ { "", "" }
+};
+MODULE_DEVICE_TABLE(vio, hvc_driver_table);
+
+typedef enum hv_protocol {
+ HV_PROTOCOL_RAW,
+ HV_PROTOCOL_HVSI
+} hv_protocol_t;
+
+struct hvterm_priv {
+ u32 termno; /* HV term number */
+ hv_protocol_t proto; /* Raw data or HVSI packets */
+ struct hvsi_priv hvsi; /* HVSI specific data */
+ spinlock_t buf_lock;
+ char buf[SIZE_VIO_GET_CHARS];
+ int left;
+ int offset;
+};
+static struct hvterm_priv *hvterm_privs[MAX_NR_HVC_CONSOLES];
+/* For early boot console */
+static struct hvterm_priv hvterm_priv0;
+
+static int hvterm_raw_get_chars(uint32_t vtermno, char *buf, int count)
+{
+ struct hvterm_priv *pv = hvterm_privs[vtermno];
+ unsigned long i;
+ unsigned long flags;
+ int got;
+
+ if (WARN_ON(!pv))
+ return 0;
+
+ spin_lock_irqsave(&pv->buf_lock, flags);
+
+ if (pv->left == 0) {
+ pv->offset = 0;
+ pv->left = hvc_get_chars(pv->termno, pv->buf, count);
+
+ /*
+ * Work around a HV bug where it gives us a null
+ * after every \r. -- paulus
+ */
+ for (i = 1; i < pv->left; ++i) {
+ if (pv->buf[i] == 0 && pv->buf[i-1] == '\r') {
+ --pv->left;
+ if (i < pv->left) {
+ memmove(&pv->buf[i], &pv->buf[i+1],
+ pv->left - i);
+ }
+ }
+ }
+ }
+
+ got = min(count, pv->left);
+ memcpy(buf, &pv->buf[pv->offset], got);
+ pv->offset += got;
+ pv->left -= got;
+
+ spin_unlock_irqrestore(&pv->buf_lock, flags);
+
+ return got;
+}
+
+static int hvterm_raw_put_chars(uint32_t vtermno, const char *buf, int count)
+{
+ struct hvterm_priv *pv = hvterm_privs[vtermno];
+
+ if (WARN_ON(!pv))
+ return 0;
+
+ return hvc_put_chars(pv->termno, buf, count);
+}
+
+static const struct hv_ops hvterm_raw_ops = {
+ .get_chars = hvterm_raw_get_chars,
+ .put_chars = hvterm_raw_put_chars,
+ .notifier_add = notifier_add_irq,
+ .notifier_del = notifier_del_irq,
+ .notifier_hangup = notifier_hangup_irq,
+};
+
+static int hvterm_hvsi_get_chars(uint32_t vtermno, char *buf, int count)
+{
+ struct hvterm_priv *pv = hvterm_privs[vtermno];
+
+ if (WARN_ON(!pv))
+ return 0;
+
+ return hvsilib_get_chars(&pv->hvsi, buf, count);
+}
+
+static int hvterm_hvsi_put_chars(uint32_t vtermno, const char *buf, int count)
+{
+ struct hvterm_priv *pv = hvterm_privs[vtermno];
+
+ if (WARN_ON(!pv))
+ return 0;
+
+ return hvsilib_put_chars(&pv->hvsi, buf, count);
+}
+
+static int hvterm_hvsi_open(struct hvc_struct *hp, int data)
+{
+ struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
+ int rc;
+
+ pr_devel("HVSI@%x: open !\n", pv->termno);
+
+ rc = notifier_add_irq(hp, data);
+ if (rc)
+ return rc;
+
+ return hvsilib_open(&pv->hvsi, hp);
+}
+
+static void hvterm_hvsi_close(struct hvc_struct *hp, int data)
+{
+ struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
+
+ pr_devel("HVSI@%x: do close !\n", pv->termno);
+
+ hvsilib_close(&pv->hvsi, hp);
+
+ notifier_del_irq(hp, data);
+}
+
+void hvterm_hvsi_hangup(struct hvc_struct *hp, int data)
+{
+ struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
+
+ pr_devel("HVSI@%x: do hangup !\n", pv->termno);
+
+ hvsilib_close(&pv->hvsi, hp);
+
+ notifier_hangup_irq(hp, data);
+}
+
+static int hvterm_hvsi_tiocmget(struct hvc_struct *hp)
+{
+ struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
+
+ if (!pv)
+ return -EINVAL;
+ return pv->hvsi.mctrl;
+}
+
+static int hvterm_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set,
+ unsigned int clear)
+{
+ struct hvterm_priv *pv = hvterm_privs[hp->vtermno];
+
+ pr_devel("HVSI@%x: Set modem control, set=%x,clr=%x\n",
+ pv->termno, set, clear);
+
+ if (set & TIOCM_DTR)
+ hvsilib_write_mctrl(&pv->hvsi, 1);
+ else if (clear & TIOCM_DTR)
+ hvsilib_write_mctrl(&pv->hvsi, 0);
+
+ return 0;
+}
+
+static const struct hv_ops hvterm_hvsi_ops = {
+ .get_chars = hvterm_hvsi_get_chars,
+ .put_chars = hvterm_hvsi_put_chars,
+ .notifier_add = hvterm_hvsi_open,
+ .notifier_del = hvterm_hvsi_close,
+ .notifier_hangup = hvterm_hvsi_hangup,
+ .tiocmget = hvterm_hvsi_tiocmget,
+ .tiocmset = hvterm_hvsi_tiocmset,
+};
+
+static int __devinit hvc_vio_probe(struct vio_dev *vdev,
+ const struct vio_device_id *id)
+{
+ const struct hv_ops *ops;
+ struct hvc_struct *hp;
+ struct hvterm_priv *pv;
+ hv_protocol_t proto;
+ int i, termno = -1;
+
+ /* probed with invalid parameters. */
+ if (!vdev || !id)
+ return -EPERM;
+
+ if (of_device_is_compatible(vdev->dev.of_node, "hvterm1")) {
+ proto = HV_PROTOCOL_RAW;
+ ops = &hvterm_raw_ops;
+ } else if (of_device_is_compatible(vdev->dev.of_node, "hvterm-protocol")) {
+ proto = HV_PROTOCOL_HVSI;
+ ops = &hvterm_hvsi_ops;
+ } else {
+ pr_err("hvc_vio: Unkown protocol for %s\n", vdev->dev.of_node->full_name);
+ return -ENXIO;
+ }
+
+ pr_devel("hvc_vio_probe() device %s, using %s protocol\n",
+ vdev->dev.of_node->full_name,
+ proto == HV_PROTOCOL_RAW ? "raw" : "hvsi");
+
+ /* Is it our boot one ? */
+ if (hvterm_privs[0] == &hvterm_priv0 &&
+ vdev->unit_address == hvterm_priv0.termno) {
+ pv = hvterm_privs[0];
+ termno = 0;
+ pr_devel("->boot console, using termno 0\n");
+ }
+ /* nope, allocate a new one */
+ else {
+ for (i = 0; i < MAX_NR_HVC_CONSOLES && termno < 0; i++)
+ if (!hvterm_privs[i])
+ termno = i;
+ pr_devel("->non-boot console, using termno %d\n", termno);
+ if (termno < 0)
+ return -ENODEV;
+ pv = kzalloc(sizeof(struct hvterm_priv), GFP_KERNEL);
+ if (!pv)
+ return -ENOMEM;
+ pv->termno = vdev->unit_address;
+ pv->proto = proto;
+ spin_lock_init(&pv->buf_lock);
+ hvterm_privs[termno] = pv;
+ hvsilib_init(&pv->hvsi, hvc_get_chars, hvc_put_chars,
+ pv->termno, 0);
+ }
+
+ hp = hvc_alloc(termno, vdev->irq, ops, MAX_VIO_PUT_CHARS);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+ dev_set_drvdata(&vdev->dev, hp);
+
+ return 0;
+}
+
+static int __devexit hvc_vio_remove(struct vio_dev *vdev)
+{
+ struct hvc_struct *hp = dev_get_drvdata(&vdev->dev);
+ int rc, termno;
+
+ termno = hp->vtermno;
+ rc = hvc_remove(hp);
+ if (rc == 0) {
+ if (hvterm_privs[termno] != &hvterm_priv0)
+ kfree(hvterm_privs[termno]);
+ hvterm_privs[termno] = NULL;
+ }
+ return rc;
+}
+
+static struct vio_driver hvc_vio_driver = {
+ .id_table = hvc_driver_table,
+ .probe = hvc_vio_probe,
+ .remove = __devexit_p(hvc_vio_remove),
+ .driver = {
+ .name = hvc_driver_name,
+ .owner = THIS_MODULE,
+ }
+};
+
+static int __init hvc_vio_init(void)
+{
+ int rc;
+
+ if (firmware_has_feature(FW_FEATURE_ISERIES))
+ return -EIO;
+
+ /* Register as a vio device to receive callbacks */
+ rc = vio_register_driver(&hvc_vio_driver);
+
+ return rc;
+}
+module_init(hvc_vio_init); /* after drivers/char/hvc_console.c */
+
+static void __exit hvc_vio_exit(void)
+{
+ vio_unregister_driver(&hvc_vio_driver);
+}
+module_exit(hvc_vio_exit);
+
+static void udbg_hvc_putc(char c)
+{
+ int count = -1;
+
+ if (c == '\n')
+ udbg_hvc_putc('\r');
+
+ do {
+ switch(hvterm_priv0.proto) {
+ case HV_PROTOCOL_RAW:
+ count = hvterm_raw_put_chars(0, &c, 1);
+ break;
+ case HV_PROTOCOL_HVSI:
+ count = hvterm_hvsi_put_chars(0, &c, 1);
+ break;
+ }
+ } while(count == 0);
+}
+
+static int udbg_hvc_getc_poll(void)
+{
+ int rc = 0;
+ char c;
+
+ switch(hvterm_priv0.proto) {
+ case HV_PROTOCOL_RAW:
+ rc = hvterm_raw_get_chars(0, &c, 1);
+ break;
+ case HV_PROTOCOL_HVSI:
+ rc = hvterm_hvsi_get_chars(0, &c, 1);
+ break;
+ }
+ if (!rc)
+ return -1;
+ return c;
+}
+
+static int udbg_hvc_getc(void)
+{
+ int ch;
+ for (;;) {
+ ch = udbg_hvc_getc_poll();
+ if (ch == -1) {
+ /* This shouldn't be needed...but... */
+ volatile unsigned long delay;
+ for (delay=0; delay < 2000000; delay++)
+ ;
+ } else {
+ return ch;
+ }
+ }
+}
+
+void __init hvc_vio_init_early(void)
+{
+ struct device_node *stdout_node;
+ const u32 *termno;
+ const char *name;
+ const struct hv_ops *ops;
+
+ /* find the boot console from /chosen/stdout */
+ if (!of_chosen)
+ return;
+ name = of_get_property(of_chosen, "linux,stdout-path", NULL);
+ if (name == NULL)
+ return;
+ stdout_node = of_find_node_by_path(name);
+ if (!stdout_node)
+ return;
+ name = of_get_property(stdout_node, "name", NULL);
+ if (!name) {
+ printk(KERN_WARNING "stdout node missing 'name' property!\n");
+ goto out;
+ }
+
+ /* Check if it's a virtual terminal */
+ if (strncmp(name, "vty", 3) != 0)
+ goto out;
+ termno = of_get_property(stdout_node, "reg", NULL);
+ if (termno == NULL)
+ goto out;
+ hvterm_priv0.termno = *termno;
+ spin_lock_init(&hvterm_priv0.buf_lock);
+ hvterm_privs[0] = &hvterm_priv0;
+
+ /* Check the protocol */
+ if (of_device_is_compatible(stdout_node, "hvterm1")) {
+ hvterm_priv0.proto = HV_PROTOCOL_RAW;
+ ops = &hvterm_raw_ops;
+ }
+ else if (of_device_is_compatible(stdout_node, "hvterm-protocol")) {
+ hvterm_priv0.proto = HV_PROTOCOL_HVSI;
+ ops = &hvterm_hvsi_ops;
+ hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars,
+ hvterm_priv0.termno, 1);
+ /* HVSI, perform the handshake now */
+ hvsilib_establish(&hvterm_priv0.hvsi);
+ } else
+ goto out;
+ udbg_putc = udbg_hvc_putc;
+ udbg_getc = udbg_hvc_getc;
+ udbg_getc_poll = udbg_hvc_getc_poll;
+#ifdef HVC_OLD_HVSI
+ /* When using the old HVSI driver don't register the HVC
+ * backend for HVSI, only do udbg
+ */
+ if (hvterm_priv0.proto == HV_PROTOCOL_HVSI)
+ goto out;
+#endif
+ add_preferred_console("hvc", 0, NULL);
+ hvc_instantiate(0, 0, ops);
+out:
+ of_node_put(stdout_node);
+}
+
+/* call this from early_init() for a working debug console on
+ * vterm capable LPAR machines
+ */
+#ifdef CONFIG_PPC_EARLY_DEBUG_LPAR
+void __init udbg_init_debug_lpar(void)
+{
+ hvterm_privs[0] = &hvterm_priv0;
+ hvterm_priv0.termno = 0;
+ hvterm_priv0.proto = HV_PROTOCOL_RAW;
+ spin_lock_init(&hvterm_priv0.buf_lock);
+ udbg_putc = udbg_hvc_putc;
+ udbg_getc = udbg_hvc_getc;
+ udbg_getc_poll = udbg_hvc_getc_poll;
+}
+#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR */
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI
+void __init udbg_init_debug_lpar_hvsi(void)
+{
+ hvterm_privs[0] = &hvterm_priv0;
+ hvterm_priv0.termno = CONFIG_PPC_EARLY_DEBUG_HVSI_VTERMNO;
+ hvterm_priv0.proto = HV_PROTOCOL_HVSI;
+ spin_lock_init(&hvterm_priv0.buf_lock);
+ udbg_putc = udbg_hvc_putc;
+ udbg_getc = udbg_hvc_getc;
+ udbg_getc_poll = udbg_hvc_getc_poll;
+ hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars,
+ hvterm_priv0.termno, 1);
+ hvsilib_establish(&hvterm_priv0.hvsi);
+}
+#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI */
diff --git a/drivers/tty/hvc/hvc_xen.c b/drivers/tty/hvc/hvc_xen.c
new file mode 100644
index 00000000000..52fdf60bdbe
--- /dev/null
+++ b/drivers/tty/hvc/hvc_xen.c
@@ -0,0 +1,273 @@
+/*
+ * xen console driver interface to hvc_console.c
+ *
+ * (c) 2007 Gerd Hoffmann <kraxel@suse.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/types.h>
+
+#include <asm/xen/hypervisor.h>
+
+#include <xen/xen.h>
+#include <xen/page.h>
+#include <xen/events.h>
+#include <xen/interface/io/console.h>
+#include <xen/hvc-console.h>
+
+#include "hvc_console.h"
+
+#define HVC_COOKIE 0x58656e /* "Xen" in hex */
+
+static struct hvc_struct *hvc;
+static int xencons_irq;
+
+/* ------------------------------------------------------------------ */
+
+static unsigned long console_pfn = ~0ul;
+
+static inline struct xencons_interface *xencons_interface(void)
+{
+ if (console_pfn == ~0ul)
+ return mfn_to_virt(xen_start_info->console.domU.mfn);
+ else
+ return __va(console_pfn << PAGE_SHIFT);
+}
+
+static inline void notify_daemon(void)
+{
+ /* Use evtchn: this is called early, before irq is set up. */
+ notify_remote_via_evtchn(xen_start_info->console.domU.evtchn);
+}
+
+static int __write_console(const char *data, int len)
+{
+ struct xencons_interface *intf = xencons_interface();
+ XENCONS_RING_IDX cons, prod;
+ int sent = 0;
+
+ cons = intf->out_cons;
+ prod = intf->out_prod;
+ mb(); /* update queue values before going on */
+ BUG_ON((prod - cons) > sizeof(intf->out));
+
+ while ((sent < len) && ((prod - cons) < sizeof(intf->out)))
+ intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++];
+
+ wmb(); /* write ring before updating pointer */
+ intf->out_prod = prod;
+
+ if (sent)
+ notify_daemon();
+ return sent;
+}
+
+static int domU_write_console(uint32_t vtermno, const char *data, int len)
+{
+ int ret = len;
+
+ /*
+ * Make sure the whole buffer is emitted, polling if
+ * necessary. We don't ever want to rely on the hvc daemon
+ * because the most interesting console output is when the
+ * kernel is crippled.
+ */
+ while (len) {
+ int sent = __write_console(data, len);
+
+ data += sent;
+ len -= sent;
+
+ if (unlikely(len))
+ HYPERVISOR_sched_op(SCHEDOP_yield, NULL);
+ }
+
+ return ret;
+}
+
+static int domU_read_console(uint32_t vtermno, char *buf, int len)
+{
+ struct xencons_interface *intf = xencons_interface();
+ XENCONS_RING_IDX cons, prod;
+ int recv = 0;
+
+ cons = intf->in_cons;
+ prod = intf->in_prod;
+ mb(); /* get pointers before reading ring */
+ BUG_ON((prod - cons) > sizeof(intf->in));
+
+ while (cons != prod && recv < len)
+ buf[recv++] = intf->in[MASK_XENCONS_IDX(cons++, intf->in)];
+
+ mb(); /* read ring before consuming */
+ intf->in_cons = cons;
+
+ notify_daemon();
+ return recv;
+}
+
+static struct hv_ops domU_hvc_ops = {
+ .get_chars = domU_read_console,
+ .put_chars = domU_write_console,
+ .notifier_add = notifier_add_irq,
+ .notifier_del = notifier_del_irq,
+ .notifier_hangup = notifier_hangup_irq,
+};
+
+static int dom0_read_console(uint32_t vtermno, char *buf, int len)
+{
+ return HYPERVISOR_console_io(CONSOLEIO_read, len, buf);
+}
+
+/*
+ * Either for a dom0 to write to the system console, or a domU with a
+ * debug version of Xen
+ */
+static int dom0_write_console(uint32_t vtermno, const char *str, int len)
+{
+ int rc = HYPERVISOR_console_io(CONSOLEIO_write, len, (char *)str);
+ if (rc < 0)
+ return 0;
+
+ return len;
+}
+
+static struct hv_ops dom0_hvc_ops = {
+ .get_chars = dom0_read_console,
+ .put_chars = dom0_write_console,
+ .notifier_add = notifier_add_irq,
+ .notifier_del = notifier_del_irq,
+ .notifier_hangup = notifier_hangup_irq,
+};
+
+static int __init xen_hvc_init(void)
+{
+ struct hvc_struct *hp;
+ struct hv_ops *ops;
+
+ if (!xen_pv_domain())
+ return -ENODEV;
+
+ if (xen_initial_domain()) {
+ ops = &dom0_hvc_ops;
+ xencons_irq = bind_virq_to_irq(VIRQ_CONSOLE, 0);
+ } else {
+ if (!xen_start_info->console.domU.evtchn)
+ return -ENODEV;
+
+ ops = &domU_hvc_ops;
+ xencons_irq = bind_evtchn_to_irq(xen_start_info->console.domU.evtchn);
+ }
+ if (xencons_irq < 0)
+ xencons_irq = 0; /* NO_IRQ */
+ else
+ irq_set_noprobe(xencons_irq);
+
+ hp = hvc_alloc(HVC_COOKIE, xencons_irq, ops, 256);
+ if (IS_ERR(hp))
+ return PTR_ERR(hp);
+
+ hvc = hp;
+
+ console_pfn = mfn_to_pfn(xen_start_info->console.domU.mfn);
+
+ return 0;
+}
+
+void xen_console_resume(void)
+{
+ if (xencons_irq)
+ rebind_evtchn_irq(xen_start_info->console.domU.evtchn, xencons_irq);
+}
+
+static void __exit xen_hvc_fini(void)
+{
+ if (hvc)
+ hvc_remove(hvc);
+}
+
+static int xen_cons_init(void)
+{
+ struct hv_ops *ops;
+
+ if (!xen_pv_domain())
+ return 0;
+
+ if (xen_initial_domain())
+ ops = &dom0_hvc_ops;
+ else
+ ops = &domU_hvc_ops;
+
+ hvc_instantiate(HVC_COOKIE, 0, ops);
+ return 0;
+}
+
+module_init(xen_hvc_init);
+module_exit(xen_hvc_fini);
+console_initcall(xen_cons_init);
+
+#ifdef CONFIG_EARLY_PRINTK
+static void xenboot_write_console(struct console *console, const char *string,
+ unsigned len)
+{
+ unsigned int linelen, off = 0;
+ const char *pos;
+
+ dom0_write_console(0, string, len);
+
+ if (xen_initial_domain())
+ return;
+
+ domU_write_console(0, "(early) ", 8);
+ while (off < len && NULL != (pos = strchr(string+off, '\n'))) {
+ linelen = pos-string+off;
+ if (off + linelen > len)
+ break;
+ domU_write_console(0, string+off, linelen);
+ domU_write_console(0, "\r\n", 2);
+ off += linelen + 1;
+ }
+ if (off < len)
+ domU_write_console(0, string+off, len-off);
+}
+
+struct console xenboot_console = {
+ .name = "xenboot",
+ .write = xenboot_write_console,
+ .flags = CON_PRINTBUFFER | CON_BOOT | CON_ANYTIME,
+};
+#endif /* CONFIG_EARLY_PRINTK */
+
+void xen_raw_console_write(const char *str)
+{
+ dom0_write_console(0, str, strlen(str));
+}
+
+void xen_raw_printk(const char *fmt, ...)
+{
+ static char buf[512];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ xen_raw_console_write(buf);
+}
diff --git a/drivers/tty/hvc/hvcs.c b/drivers/tty/hvc/hvcs.c
new file mode 100644
index 00000000000..b9040bec36b
--- /dev/null
+++ b/drivers/tty/hvc/hvcs.c
@@ -0,0 +1,1616 @@
+/*
+ * 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 kref construct.
+ *
+ * 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/kref.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/slab.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/powerepc/include/asm/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 similarly 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/powerpc/platforms/pseries/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 ktermios hvcs_tty_termios = {
+ .c_iflag = IGNBRK | IGNPAR,
+ .c_oflag = OPOST,
+ .c_cflag = B38400 | CS8 | CREAD | HUPCL,
+ .c_cc = INIT_C_CC,
+ .c_ispeed = 38400,
+ .c_ospeed = 38400
+};
+
+/*
+ * 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);
+
+static const char hvcs_driver_name[] = "hvcs";
+static const char hvcs_device_node[] = "hvcs";
+static const 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;
+ 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 kref is valid before a tty is connected and
+ * stays valid after the tty is disconnected. These shouldn't be
+ * whacked until the kobject refcount reaches zero though some entries
+ * may be changed via sysfs initiatives.
+ */
+ struct kref kref; /* 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 kref to its containing object */
+#define from_kref(k) container_of(k, struct hvcs_struct, kref)
+
+static LIST_HEAD(hvcs_structs);
+static DEFINE_SPINLOCK(hvcs_structs_lock);
+static DEFINE_MUTEX(hvcs_init_mutex);
+
+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);
+
+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 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 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);
+static int __devinit hvcs_initialize(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 inline struct hvcs_struct *from_vio_dev(struct vio_dev *viod)
+{
+ return dev_get_drvdata(&viod->dev);
+}
+/* The sysfs interface for the driver and devices */
+
+static ssize_t hvcs_partner_vtys_show(struct device *dev, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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 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_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 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 committed 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;
+
+ 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_buffer_request_room(tty, HVCS_BUFF_LEN) >= HVCS_BUFF_LEN) {
+ got = hvc_get_chars(unit_address,
+ &buf[0],
+ HVCS_BUFF_LEN);
+ tty_insert_flip_string(tty, buf, got);
+ }
+
+ /* 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);
+ /* This is synch because tty->low_latency == 1 */
+ if(got)
+ 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"},
+ { "", "" }
+};
+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 kref ref count reaches zero */
+static void destroy_hvcs_struct(struct kref *kref)
+{
+ struct hvcs_struct *hvcsd = from_kref(kref);
+ 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);
+
+ sysfs_remove_group(&vdev->dev.kobj, &hvcs_attr_group);
+
+ kfree(hvcsd);
+}
+
+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, rc;
+ int retval;
+
+ if (!dev || !id) {
+ printk(KERN_ERR "HVCS: probed with invalid parameter.\n");
+ return -EPERM;
+ }
+
+ /* Make sure we are properly initialized */
+ rc = hvcs_initialize();
+ if (rc) {
+ pr_err("HVCS: Failed to initialize core driver.\n");
+ return rc;
+ }
+
+ /* early to avoid cleanup on failure */
+ index = hvcs_get_index();
+ if (index < 0) {
+ return -EFAULT;
+ }
+
+ hvcsd = kzalloc(sizeof(*hvcsd), GFP_KERNEL);
+ if (!hvcsd)
+ return -ENODEV;
+
+
+ spin_lock_init(&hvcsd->lock);
+ /* Automatically incs the refcount the first time */
+ kref_init(&hvcsd->kref);
+
+ hvcsd->vdev = dev;
+ dev_set_drvdata(&dev->dev, 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);
+
+ retval = sysfs_create_group(&dev->dev.kobj, &hvcs_attr_group);
+ if (retval) {
+ printk(KERN_ERR "HVCS: Can't create sysfs attrs for vty-server@%X\n",
+ hvcsd->vdev->unit_address);
+ return retval;
+ }
+
+ 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_get_drvdata(&dev->dev);
+ unsigned long flags;
+ struct tty_struct *tty;
+
+ if (!hvcsd)
+ return -ENODEV;
+
+ /* By this time the vty-server won't be getting any more interrupts */
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ tty = hvcsd->tty;
+
+ 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.
+ */
+ kref_put(&hvcsd->kref, destroy_hvcs_struct);
+
+ /*
+ * 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 = {
+ .id_table = hvcs_driver_table,
+ .probe = hvcs_probe,
+ .remove = __devexit_p(hvcs_remove),
+ .driver = {
+ .name = hvcs_driver_name,
+ .owner = THIS_MODULE,
+ }
+};
+
+/* 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,
+ 0, "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 kref 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.
+ */
+static 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) {
+ kref_get(&hvcsd->kref);
+ 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;
+
+ if (tty->driver_data)
+ goto fast_open;
+
+ /*
+ * Is there a vty-server that shares the same index?
+ * This function increments the kref 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;
+
+ 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)))) {
+ kref_put(&hvcsd->kref, destroy_hvcs_struct);
+ 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);
+ kref_get(&hvcsd->kref);
+ 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:
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ kref_put(&hvcsd->kref, destroy_hvcs_struct);
+
+ 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;
+ 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);
+ 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_from_close(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);
+ kref_put(&hvcsd->kref, destroy_hvcs_struct);
+ 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);
+ kref_put(&hvcsd->kref, destroy_hvcs_struct);
+}
+
+static void hvcs_hangup(struct tty_struct * tty)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+ unsigned long flags;
+ int temp_open_count;
+ int irq = NO_IRQ;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ /* Preserve this so that we know how many kref refs to put */
+ temp_open_count = hvcsd->open_count;
+
+ /*
+ * Don't kref put inside the spinlock because the destruction
+ * callback may use the spinlock and it may get called before the
+ * spinlock has been released.
+ */
+ 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 kref_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.
+ */
+ kref_put(&hvcsd->kref, destroy_hvcs_struct);
+ }
+}
+
+/*
+ * 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 succeeded 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 guarantee 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 const 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. */
+ kfree(hvcs_index_list);
+ hvcs_index_list = NULL;
+ hvcs_index_count = 0;
+}
+
+static int __devinit hvcs_initialize(void)
+{
+ int rc, num_ttys_to_alloc;
+
+ mutex_lock(&hvcs_init_mutex);
+ if (hvcs_task) {
+ mutex_unlock(&hvcs_init_mutex);
+ return 0;
+ }
+
+ /* 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)) {
+ rc = -ENOMEM;
+ goto index_fail;
+ }
+
+ hvcs_tty_driver->owner = THIS_MODULE;
+
+ hvcs_tty_driver->driver_name = hvcs_driver_name;
+ hvcs_tty_driver->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");
+ rc = -EIO;
+ goto register_fail;
+ }
+
+ hvcs_pi_buff = (unsigned long *) __get_free_page(GFP_KERNEL);
+ if (!hvcs_pi_buff) {
+ rc = -ENOMEM;
+ goto buff_alloc_fail;
+ }
+
+ hvcs_task = kthread_run(khvcsd, NULL, "khvcsd");
+ if (IS_ERR(hvcs_task)) {
+ printk(KERN_ERR "HVCS: khvcsd creation failed.\n");
+ rc = -EIO;
+ goto kthread_fail;
+ }
+ mutex_unlock(&hvcs_init_mutex);
+ return 0;
+
+kthread_fail:
+ free_page((unsigned long)hvcs_pi_buff);
+buff_alloc_fail:
+ tty_unregister_driver(hvcs_tty_driver);
+register_fail:
+ hvcs_free_index_list();
+index_fail:
+ put_tty_driver(hvcs_tty_driver);
+ hvcs_tty_driver = NULL;
+ mutex_unlock(&hvcs_init_mutex);
+ return rc;
+}
+
+static int __init hvcs_module_init(void)
+{
+ int rc = vio_register_driver(&hvcs_vio_driver);
+ if (rc) {
+ printk(KERN_ERR "HVCS: can't register vio driver\n");
+ return rc;
+ }
+
+ pr_info("HVCS: Driver registered.\n");
+
+ /* This needs to be done AFTER the vio_register_driver() call or else
+ * the kobjects won't be initialized properly.
+ */
+ rc = driver_create_file(&(hvcs_vio_driver.driver), &driver_attr_rescan);
+ if (rc)
+ pr_warning(KERN_ERR "HVCS: Failed to create rescan file (err %d)\n", rc);
+
+ return 0;
+}
+
+static void __exit hvcs_module_exit(void)
+{
+ /*
+ * This driver receives hvcs_remove callbacks for each device upon
+ * module removal.
+ */
+ vio_unregister_driver(&hvcs_vio_driver);
+ if (!hvcs_task)
+ return;
+
+ /*
+ * 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);
+ free_page((unsigned long)hvcs_pi_buff);
+ hvcs_pi_buff = NULL;
+ spin_unlock(&hvcs_pi_lock);
+
+ driver_remove_file(&hvcs_vio_driver.driver, &driver_attr_rescan);
+
+ 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);
diff --git a/drivers/tty/hvc/hvsi.c b/drivers/tty/hvc/hvsi.c
new file mode 100644
index 00000000000..cdfa3e02d62
--- /dev/null
+++ b/drivers/tty/hvc/hvsi.c
@@ -0,0 +1,1253 @@
+/*
+ * 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/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>
+#include <asm/hvsi.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 delayed_work 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;
+ 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
+
+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 = (hp->state < ARRAY_SIZE(state_names))
+ ? state_names[hp->state] : "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", __func__, 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);
+}
+
+static int hvsi_read(struct hvsi_struct *hp, char *buf, int count)
+{
+ unsigned long got;
+
+ got = hvc_get_chars(hp->vtermno, buf, count);
+
+ return got;
+}
+
+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 userland hasn't done an open(2) yet, hp->tty is NULL. */
+ if (hp->tty && !(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.hdr.type = VS_QUERY_RESPONSE_PACKET_HEADER;
+ packet.hdr.len = sizeof(struct hvsi_query_response);
+ packet.hdr.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", __func__, packet.hdr.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.hdr.len);
+
+ wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len);
+ if (wrote != packet.hdr.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->hdr.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);
+ 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", __func__);
+ 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", __func__);
+ 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", __func__);
+ return 0;
+ }
+
+ pr_debug("%s: got %i bytes\n", __func__, 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", __func__,
+ 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", __func__);
+ /*
+ * 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", __func__,
+ 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 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", __func__);
+
+ 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); /* 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.hdr.type = VS_QUERY_PACKET_HEADER;
+ packet.hdr.len = sizeof(struct hvsi_query);
+ packet.hdr.seqno = atomic_inc_return(&hp->seqno);
+ packet.verb = verb;
+
+ pr_debug("%s: sending %i bytes\n", __func__, packet.hdr.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.hdr.len);
+
+ wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len);
+ if (wrote != packet.hdr.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", __func__, 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.hdr.type = VS_CONTROL_PACKET_HEADER,
+ packet.hdr.seqno = atomic_inc_return(&hp->seqno);
+ packet.hdr.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", __func__, packet.hdr.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.hdr.len);
+
+ wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len);
+ if (wrote != packet.hdr.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(struct work_struct *work)
+{
+ struct hvsi_struct *hp =
+ container_of(work, struct hvsi_struct, handshaker);
+
+ 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.hdr.type = VS_DATA_PACKET_HEADER;
+ packet.hdr.seqno = atomic_inc_return(&hp->seqno);
+ packet.hdr.len = count + sizeof(struct hvsi_header);
+ memcpy(&packet.data, buf, count);
+
+ ret = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len);
+ if (ret == packet.hdr.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.hdr.type = VS_CONTROL_PACKET_HEADER;
+ packet.hdr.seqno = atomic_inc_return(&hp->seqno);
+ packet.hdr.len = 6;
+ packet.verb = VSV_CLOSE_PROTOCOL;
+
+ pr_debug("%s: sending %i bytes\n", __func__, packet.hdr.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.hdr.len);
+
+ hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.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", __func__);
+
+ if (line < 0 || line >= hvsi_count)
+ return -ENODEV;
+ hp = &hvsi_ports[line];
+
+ tty->driver_data = hp;
+
+ 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_sync(&hp->writer);
+ flush_work_sync(&hp->handshaker);
+
+ /*
+ * 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", __func__);
+
+ 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", __func__);
+
+ 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", __func__, 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(struct work_struct *work)
+{
+ struct hvsi_struct *hp =
+ container_of(work, struct hvsi_struct, writer.work);
+ 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", __func__, 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", __func__,
+ jiffies - start_j);
+ start_j = 0;
+#endif /* DEBUG */
+ wake_up_all(&hp->emptyq);
+ tty_wakeup(hp->tty);
+ }
+
+out:
+ spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static int hvsi_write_room(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = tty->driver_data;
+
+ return N_OUTBUF - hp->n_outbuf;
+}
+
+static int hvsi_chars_in_buffer(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = 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", __func__, 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", __func__);
+ 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", __func__, 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 = tty->driver_data;
+
+ pr_debug("%s\n", __func__);
+
+ h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE);
+}
+
+static void hvsi_unthrottle(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = tty->driver_data;
+ unsigned long flags;
+ int shouldflip = 0;
+
+ pr_debug("%s\n", __func__);
+
+ 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 hvsi_struct *hp = tty->driver_data;
+
+ hvsi_get_mctrl(hp);
+ return hp->mctrl;
+}
+
+static int hvsi_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct hvsi_struct *hp = 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 const 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->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->init_termios.c_ispeed = 9600;
+ hvsi_driver->init_termios.c_ospeed = 9600;
+ 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, 0, "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_DEBUG "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;
+ int ret;
+
+ if (console->index < 0 || console->index >= hvsi_count)
+ return -1;
+ hp = &hvsi_ports[console->index];
+
+ /* 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_console = {
+ .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;
+ const uint32_t *vtermno, *irq;
+
+ vtermno = of_get_property(vty, "reg", NULL);
+ irq = of_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_DELAYED_WORK(&hp->writer, hvsi_write_worker);
+ INIT_WORK(&hp->handshaker, hvsi_handshaker);
+ 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 = irq_create_mapping(NULL, irq[0]);
+ if (hp->virq == NO_IRQ) {
+ printk(KERN_ERR "%s: couldn't create irq mapping for 0x%x\n",
+ __func__, irq[0]);
+ continue;
+ }
+
+ hvsi_count++;
+ }
+
+ if (hvsi_count)
+ register_console(&hvsi_console);
+ return 0;
+}
+console_initcall(hvsi_console_init);
diff --git a/drivers/tty/hvc/hvsi_lib.c b/drivers/tty/hvc/hvsi_lib.c
new file mode 100644
index 00000000000..6f4dd83d869
--- /dev/null
+++ b/drivers/tty/hvc/hvsi_lib.c
@@ -0,0 +1,426 @@
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/console.h>
+#include <asm/hvsi.h>
+
+#include "hvc_console.h"
+
+static int hvsi_send_packet(struct hvsi_priv *pv, struct hvsi_header *packet)
+{
+ packet->seqno = atomic_inc_return(&pv->seqno);
+
+ /* Assumes that always succeeds, works in practice */
+ return pv->put_chars(pv->termno, (char *)packet, packet->len);
+}
+
+static void hvsi_start_handshake(struct hvsi_priv *pv)
+{
+ struct hvsi_query q;
+
+ /* Reset state */
+ pv->established = 0;
+ atomic_set(&pv->seqno, 0);
+
+ pr_devel("HVSI@%x: Handshaking started\n", pv->termno);
+
+ /* Send version query */
+ q.hdr.type = VS_QUERY_PACKET_HEADER;
+ q.hdr.len = sizeof(struct hvsi_query);
+ q.verb = VSV_SEND_VERSION_NUMBER;
+ hvsi_send_packet(pv, &q.hdr);
+}
+
+static int hvsi_send_close(struct hvsi_priv *pv)
+{
+ struct hvsi_control ctrl;
+
+ pv->established = 0;
+
+ ctrl.hdr.type = VS_CONTROL_PACKET_HEADER;
+ ctrl.hdr.len = sizeof(struct hvsi_control);
+ ctrl.verb = VSV_CLOSE_PROTOCOL;
+ return hvsi_send_packet(pv, &ctrl.hdr);
+}
+
+static void hvsi_cd_change(struct hvsi_priv *pv, int cd)
+{
+ if (cd)
+ pv->mctrl |= TIOCM_CD;
+ else {
+ pv->mctrl &= ~TIOCM_CD;
+
+ /* We copy the existing hvsi driver semantics
+ * here which are to trigger a hangup when
+ * we get a carrier loss.
+ * Closing our connection to the server will
+ * do just that.
+ */
+ if (!pv->is_console && pv->opened) {
+ pr_devel("HVSI@%x Carrier lost, hanging up !\n",
+ pv->termno);
+ hvsi_send_close(pv);
+ }
+ }
+}
+
+static void hvsi_got_control(struct hvsi_priv *pv)
+{
+ struct hvsi_control *pkt = (struct hvsi_control *)pv->inbuf;
+
+ switch (pkt->verb) {
+ case VSV_CLOSE_PROTOCOL:
+ /* We restart the handshaking */
+ hvsi_start_handshake(pv);
+ break;
+ case VSV_MODEM_CTL_UPDATE:
+ /* Transition of carrier detect */
+ hvsi_cd_change(pv, pkt->word & HVSI_TSCD);
+ break;
+ }
+}
+
+static void hvsi_got_query(struct hvsi_priv *pv)
+{
+ struct hvsi_query *pkt = (struct hvsi_query *)pv->inbuf;
+ struct hvsi_query_response r;
+
+ /* We only handle version queries */
+ if (pkt->verb != VSV_SEND_VERSION_NUMBER)
+ return;
+
+ pr_devel("HVSI@%x: Got version query, sending response...\n",
+ pv->termno);
+
+ /* Send version response */
+ r.hdr.type = VS_QUERY_RESPONSE_PACKET_HEADER;
+ r.hdr.len = sizeof(struct hvsi_query_response);
+ r.verb = VSV_SEND_VERSION_NUMBER;
+ r.u.version = HVSI_VERSION;
+ r.query_seqno = pkt->hdr.seqno;
+ hvsi_send_packet(pv, &r.hdr);
+
+ /* Assume protocol is open now */
+ pv->established = 1;
+}
+
+static void hvsi_got_response(struct hvsi_priv *pv)
+{
+ struct hvsi_query_response *r =
+ (struct hvsi_query_response *)pv->inbuf;
+
+ switch(r->verb) {
+ case VSV_SEND_MODEM_CTL_STATUS:
+ hvsi_cd_change(pv, r->u.mctrl_word & HVSI_TSCD);
+ pv->mctrl_update = 1;
+ break;
+ }
+}
+
+static int hvsi_check_packet(struct hvsi_priv *pv)
+{
+ u8 len, type;
+
+ /* Check header validity. If it's invalid, we ditch
+ * the whole buffer and hope we eventually resync
+ */
+ if (pv->inbuf[0] < 0xfc) {
+ pv->inbuf_len = pv->inbuf_pktlen = 0;
+ return 0;
+ }
+ type = pv->inbuf[0];
+ len = pv->inbuf[1];
+
+ /* Packet incomplete ? */
+ if (pv->inbuf_len < len)
+ return 0;
+
+ pr_devel("HVSI@%x: Got packet type %x len %d bytes:\n",
+ pv->termno, type, len);
+
+ /* We have a packet, yay ! Handle it */
+ switch(type) {
+ case VS_DATA_PACKET_HEADER:
+ pv->inbuf_pktlen = len - 4;
+ pv->inbuf_cur = 4;
+ return 1;
+ case VS_CONTROL_PACKET_HEADER:
+ hvsi_got_control(pv);
+ break;
+ case VS_QUERY_PACKET_HEADER:
+ hvsi_got_query(pv);
+ break;
+ case VS_QUERY_RESPONSE_PACKET_HEADER:
+ hvsi_got_response(pv);
+ break;
+ }
+
+ /* Swallow packet and retry */
+ pv->inbuf_len -= len;
+ memmove(pv->inbuf, &pv->inbuf[len], pv->inbuf_len);
+ return 1;
+}
+
+static int hvsi_get_packet(struct hvsi_priv *pv)
+{
+ /* If we have room in the buffer, ask HV for more */
+ if (pv->inbuf_len < HVSI_INBUF_SIZE)
+ pv->inbuf_len += pv->get_chars(pv->termno,
+ &pv->inbuf[pv->inbuf_len],
+ HVSI_INBUF_SIZE - pv->inbuf_len);
+ /*
+ * If we have at least 4 bytes in the buffer, check for
+ * a full packet and retry
+ */
+ if (pv->inbuf_len >= 4)
+ return hvsi_check_packet(pv);
+ return 0;
+}
+
+int hvsilib_get_chars(struct hvsi_priv *pv, char *buf, int count)
+{
+ unsigned int tries, read = 0;
+
+ if (WARN_ON(!pv))
+ return -ENXIO;
+
+ /* If we aren't open, don't do anything in order to avoid races
+ * with connection establishment. The hvc core will call this
+ * before we have returned from notifier_add(), and we need to
+ * avoid multiple users playing with the receive buffer
+ */
+ if (!pv->opened)
+ return 0;
+
+ /* We try twice, once with what data we have and once more
+ * after we try to fetch some more from the hypervisor
+ */
+ for (tries = 1; count && tries < 2; tries++) {
+ /* Consume existing data packet */
+ if (pv->inbuf_pktlen) {
+ unsigned int l = min(count, (int)pv->inbuf_pktlen);
+ memcpy(&buf[read], &pv->inbuf[pv->inbuf_cur], l);
+ pv->inbuf_cur += l;
+ pv->inbuf_pktlen -= l;
+ count -= l;
+ read += l;
+ }
+ if (count == 0)
+ break;
+
+ /* Data packet fully consumed, move down remaning data */
+ if (pv->inbuf_cur) {
+ pv->inbuf_len -= pv->inbuf_cur;
+ memmove(pv->inbuf, &pv->inbuf[pv->inbuf_cur],
+ pv->inbuf_len);
+ pv->inbuf_cur = 0;
+ }
+
+ /* Try to get another packet */
+ if (hvsi_get_packet(pv))
+ tries--;
+ }
+ if (!pv->established) {
+ pr_devel("HVSI@%x: returning -EPIPE\n", pv->termno);
+ return -EPIPE;
+ }
+ return read;
+}
+
+int hvsilib_put_chars(struct hvsi_priv *pv, const char *buf, int count)
+{
+ struct hvsi_data dp;
+ int rc, adjcount = min(count, HVSI_MAX_OUTGOING_DATA);
+
+ if (WARN_ON(!pv))
+ return -ENODEV;
+
+ dp.hdr.type = VS_DATA_PACKET_HEADER;
+ dp.hdr.len = adjcount + sizeof(struct hvsi_header);
+ memcpy(dp.data, buf, adjcount);
+ rc = hvsi_send_packet(pv, &dp.hdr);
+ if (rc <= 0)
+ return rc;
+ return adjcount;
+}
+
+static void maybe_msleep(unsigned long ms)
+{
+ /* During early boot, IRQs are disabled, use mdelay */
+ if (irqs_disabled())
+ mdelay(ms);
+ else
+ msleep(ms);
+}
+
+int hvsilib_read_mctrl(struct hvsi_priv *pv)
+{
+ struct hvsi_query q;
+ int rc, timeout;
+
+ pr_devel("HVSI@%x: Querying modem control status...\n",
+ pv->termno);
+
+ pv->mctrl_update = 0;
+ q.hdr.type = VS_QUERY_PACKET_HEADER;
+ q.hdr.len = sizeof(struct hvsi_query);
+ q.hdr.seqno = atomic_inc_return(&pv->seqno);
+ q.verb = VSV_SEND_MODEM_CTL_STATUS;
+ rc = hvsi_send_packet(pv, &q.hdr);
+ if (rc <= 0) {
+ pr_devel("HVSI@%x: Error %d...\n", pv->termno, rc);
+ return rc;
+ }
+
+ /* Try for up to 200ms */
+ for (timeout = 0; timeout < 20; timeout++) {
+ if (!pv->established)
+ return -ENXIO;
+ if (pv->mctrl_update)
+ return 0;
+ if (!hvsi_get_packet(pv))
+ maybe_msleep(10);
+ }
+ return -EIO;
+}
+
+int hvsilib_write_mctrl(struct hvsi_priv *pv, int dtr)
+{
+ struct hvsi_control ctrl;
+ unsigned short mctrl;
+
+ mctrl = pv->mctrl;
+ if (dtr)
+ mctrl |= TIOCM_DTR;
+ else
+ mctrl &= ~TIOCM_DTR;
+ if (mctrl == pv->mctrl)
+ return 0;
+ pv->mctrl = mctrl;
+
+ pr_devel("HVSI@%x: %s DTR...\n", pv->termno,
+ dtr ? "Setting" : "Clearing");
+
+ ctrl.hdr.type = VS_CONTROL_PACKET_HEADER,
+ ctrl.hdr.len = sizeof(struct hvsi_control);
+ ctrl.verb = VSV_SET_MODEM_CTL;
+ ctrl.mask = HVSI_TSDTR;
+ ctrl.word = dtr ? HVSI_TSDTR : 0;
+ return hvsi_send_packet(pv, &ctrl.hdr);
+}
+
+void hvsilib_establish(struct hvsi_priv *pv)
+{
+ int timeout;
+
+ pr_devel("HVSI@%x: Establishing...\n", pv->termno);
+
+ /* Try for up to 200ms, there can be a packet to
+ * start the process waiting for us...
+ */
+ for (timeout = 0; timeout < 20; timeout++) {
+ if (pv->established)
+ goto established;
+ if (!hvsi_get_packet(pv))
+ maybe_msleep(10);
+ }
+
+ /* Failed, send a close connection packet just
+ * in case
+ */
+ pr_devel("HVSI@%x: ... sending close\n", pv->termno);
+
+ hvsi_send_close(pv);
+
+ /* Then restart handshake */
+
+ pr_devel("HVSI@%x: ... restarting handshake\n", pv->termno);
+
+ hvsi_start_handshake(pv);
+
+ pr_devel("HVSI@%x: ... waiting handshake\n", pv->termno);
+
+ /* Try for up to 200s */
+ for (timeout = 0; timeout < 20; timeout++) {
+ if (pv->established)
+ goto established;
+ if (!hvsi_get_packet(pv))
+ maybe_msleep(10);
+ }
+
+ if (!pv->established) {
+ pr_devel("HVSI@%x: Timeout handshaking, giving up !\n",
+ pv->termno);
+ return;
+ }
+ established:
+ /* Query modem control lines */
+
+ pr_devel("HVSI@%x: ... established, reading mctrl\n", pv->termno);
+
+ hvsilib_read_mctrl(pv);
+
+ /* Set our own DTR */
+
+ pr_devel("HVSI@%x: ... setting mctrl\n", pv->termno);
+
+ hvsilib_write_mctrl(pv, 1);
+
+ /* Set the opened flag so reads are allowed */
+ wmb();
+ pv->opened = 1;
+}
+
+int hvsilib_open(struct hvsi_priv *pv, struct hvc_struct *hp)
+{
+ pr_devel("HVSI@%x: open !\n", pv->termno);
+
+ /* Keep track of the tty data structure */
+ pv->tty = tty_kref_get(hp->tty);
+
+ hvsilib_establish(pv);
+
+ return 0;
+}
+
+void hvsilib_close(struct hvsi_priv *pv, struct hvc_struct *hp)
+{
+ unsigned long flags;
+
+ pr_devel("HVSI@%x: close !\n", pv->termno);
+
+ if (!pv->is_console) {
+ pr_devel("HVSI@%x: Not a console, tearing down\n",
+ pv->termno);
+
+ /* Clear opened, synchronize with khvcd */
+ spin_lock_irqsave(&hp->lock, flags);
+ pv->opened = 0;
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ /* Clear our own DTR */
+ if (!pv->tty || (pv->tty->termios->c_cflag & HUPCL))
+ hvsilib_write_mctrl(pv, 0);
+
+ /* Tear down the connection */
+ hvsi_send_close(pv);
+ }
+
+ if (pv->tty)
+ tty_kref_put(pv->tty);
+ pv->tty = NULL;
+}
+
+void hvsilib_init(struct hvsi_priv *pv,
+ int (*get_chars)(uint32_t termno, char *buf, int count),
+ int (*put_chars)(uint32_t termno, const char *buf,
+ int count),
+ int termno, int is_console)
+{
+ memset(pv, 0, sizeof(*pv));
+ pv->get_chars = get_chars;
+ pv->put_chars = put_chars;
+ pv->termno = termno;
+ pv->is_console = is_console;
+}
diff --git a/drivers/tty/ipwireless/Makefile b/drivers/tty/ipwireless/Makefile
new file mode 100644
index 00000000000..fe2e1730986
--- /dev/null
+++ b/drivers/tty/ipwireless/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the IPWireless driver
+#
+
+obj-$(CONFIG_IPWIRELESS) += ipwireless.o
+
+ipwireless-y := hardware.o main.o network.o tty.o
+
diff --git a/drivers/tty/ipwireless/hardware.c b/drivers/tty/ipwireless/hardware.c
new file mode 100644
index 00000000000..0aeb5a38d29
--- /dev/null
+++ b/drivers/tty/ipwireless/hardware.c
@@ -0,0 +1,1764 @@
+/*
+ * IPWireless 3G PCMCIA Network Driver
+ *
+ * Original code
+ * by Stephen Blackheath <stephen@blacksapphire.com>,
+ * Ben Martel <benm@symmetric.co.nz>
+ *
+ * Copyrighted as follows:
+ * Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
+ *
+ * Various driver changes and rewrites, port to new kernels
+ * Copyright (C) 2006-2007 Jiri Kosina
+ *
+ * Misc code cleanups and updates
+ * Copyright (C) 2007 David Sterba
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "hardware.h"
+#include "setup_protocol.h"
+#include "network.h"
+#include "main.h"
+
+static void ipw_send_setup_packet(struct ipw_hardware *hw);
+static void handle_received_SETUP_packet(struct ipw_hardware *ipw,
+ unsigned int address,
+ const unsigned char *data, int len,
+ int is_last);
+static void ipwireless_setup_timer(unsigned long data);
+static void handle_received_CTRL_packet(struct ipw_hardware *hw,
+ unsigned int channel_idx, const unsigned char *data, int len);
+
+/*#define TIMING_DIAGNOSTICS*/
+
+#ifdef TIMING_DIAGNOSTICS
+
+static struct timing_stats {
+ unsigned long last_report_time;
+ unsigned long read_time;
+ unsigned long write_time;
+ unsigned long read_bytes;
+ unsigned long write_bytes;
+ unsigned long start_time;
+};
+
+static void start_timing(void)
+{
+ timing_stats.start_time = jiffies;
+}
+
+static void end_read_timing(unsigned length)
+{
+ timing_stats.read_time += (jiffies - start_time);
+ timing_stats.read_bytes += length + 2;
+ report_timing();
+}
+
+static void end_write_timing(unsigned length)
+{
+ timing_stats.write_time += (jiffies - start_time);
+ timing_stats.write_bytes += length + 2;
+ report_timing();
+}
+
+static void report_timing(void)
+{
+ unsigned long since = jiffies - timing_stats.last_report_time;
+
+ /* If it's been more than one second... */
+ if (since >= HZ) {
+ int first = (timing_stats.last_report_time == 0);
+
+ timing_stats.last_report_time = jiffies;
+ if (!first)
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": %u us elapsed - read %lu bytes in %u us, wrote %lu bytes in %u us\n",
+ jiffies_to_usecs(since),
+ timing_stats.read_bytes,
+ jiffies_to_usecs(timing_stats.read_time),
+ timing_stats.write_bytes,
+ jiffies_to_usecs(timing_stats.write_time));
+
+ timing_stats.read_time = 0;
+ timing_stats.write_time = 0;
+ timing_stats.read_bytes = 0;
+ timing_stats.write_bytes = 0;
+ }
+}
+#else
+static void start_timing(void) { }
+static void end_read_timing(unsigned length) { }
+static void end_write_timing(unsigned length) { }
+#endif
+
+/* Imported IPW definitions */
+
+#define LL_MTU_V1 318
+#define LL_MTU_V2 250
+#define LL_MTU_MAX (LL_MTU_V1 > LL_MTU_V2 ? LL_MTU_V1 : LL_MTU_V2)
+
+#define PRIO_DATA 2
+#define PRIO_CTRL 1
+#define PRIO_SETUP 0
+
+/* Addresses */
+#define ADDR_SETUP_PROT 0
+
+/* Protocol ids */
+enum {
+ /* Identifier for the Com Data protocol */
+ TL_PROTOCOLID_COM_DATA = 0,
+
+ /* Identifier for the Com Control protocol */
+ TL_PROTOCOLID_COM_CTRL = 1,
+
+ /* Identifier for the Setup protocol */
+ TL_PROTOCOLID_SETUP = 2
+};
+
+/* Number of bytes in NL packet header (cannot do
+ * sizeof(nl_packet_header) since it's a bitfield) */
+#define NL_FIRST_PACKET_HEADER_SIZE 3
+
+/* Number of bytes in NL packet header (cannot do
+ * sizeof(nl_packet_header) since it's a bitfield) */
+#define NL_FOLLOWING_PACKET_HEADER_SIZE 1
+
+struct nl_first_packet_header {
+ unsigned char protocol:3;
+ unsigned char address:3;
+ unsigned char packet_rank:2;
+ unsigned char length_lsb;
+ unsigned char length_msb;
+};
+
+struct nl_packet_header {
+ unsigned char protocol:3;
+ unsigned char address:3;
+ unsigned char packet_rank:2;
+};
+
+/* Value of 'packet_rank' above */
+#define NL_INTERMEDIATE_PACKET 0x0
+#define NL_LAST_PACKET 0x1
+#define NL_FIRST_PACKET 0x2
+
+union nl_packet {
+ /* Network packet header of the first packet (a special case) */
+ struct nl_first_packet_header hdr_first;
+ /* Network packet header of the following packets (if any) */
+ struct nl_packet_header hdr;
+ /* Complete network packet (header + data) */
+ unsigned char rawpkt[LL_MTU_MAX];
+} __attribute__ ((__packed__));
+
+#define HW_VERSION_UNKNOWN -1
+#define HW_VERSION_1 1
+#define HW_VERSION_2 2
+
+/* IPW I/O ports */
+#define IOIER 0x00 /* Interrupt Enable Register */
+#define IOIR 0x02 /* Interrupt Source/ACK register */
+#define IODCR 0x04 /* Data Control Register */
+#define IODRR 0x06 /* Data Read Register */
+#define IODWR 0x08 /* Data Write Register */
+#define IOESR 0x0A /* Embedded Driver Status Register */
+#define IORXR 0x0C /* Rx Fifo Register (Host to Embedded) */
+#define IOTXR 0x0E /* Tx Fifo Register (Embedded to Host) */
+
+/* I/O ports and bit definitions for version 1 of the hardware */
+
+/* IER bits*/
+#define IER_RXENABLED 0x1
+#define IER_TXENABLED 0x2
+
+/* ISR bits */
+#define IR_RXINTR 0x1
+#define IR_TXINTR 0x2
+
+/* DCR bits */
+#define DCR_RXDONE 0x1
+#define DCR_TXDONE 0x2
+#define DCR_RXRESET 0x4
+#define DCR_TXRESET 0x8
+
+/* I/O ports and bit definitions for version 2 of the hardware */
+
+struct MEMCCR {
+ unsigned short reg_config_option; /* PCCOR: Configuration Option Register */
+ unsigned short reg_config_and_status; /* PCCSR: Configuration and Status Register */
+ unsigned short reg_pin_replacement; /* PCPRR: Pin Replacemant Register */
+ unsigned short reg_socket_and_copy; /* PCSCR: Socket and Copy Register */
+ unsigned short reg_ext_status; /* PCESR: Extendend Status Register */
+ unsigned short reg_io_base; /* PCIOB: I/O Base Register */
+};
+
+struct MEMINFREG {
+ unsigned short memreg_tx_old; /* TX Register (R/W) */
+ unsigned short pad1;
+ unsigned short memreg_rx_done; /* RXDone Register (R/W) */
+ unsigned short pad2;
+ unsigned short memreg_rx; /* RX Register (R/W) */
+ unsigned short pad3;
+ unsigned short memreg_pc_interrupt_ack; /* PC intr Ack Register (W) */
+ unsigned short pad4;
+ unsigned long memreg_card_present;/* Mask for Host to check (R) for
+ * CARD_PRESENT_VALUE */
+ unsigned short memreg_tx_new; /* TX2 (new) Register (R/W) */
+};
+
+#define CARD_PRESENT_VALUE (0xBEEFCAFEUL)
+
+#define MEMTX_TX 0x0001
+#define MEMRX_RX 0x0001
+#define MEMRX_RX_DONE 0x0001
+#define MEMRX_PCINTACKK 0x0001
+
+#define NL_NUM_OF_PRIORITIES 3
+#define NL_NUM_OF_PROTOCOLS 3
+#define NL_NUM_OF_ADDRESSES NO_OF_IPW_CHANNELS
+
+struct ipw_hardware {
+ unsigned int base_port;
+ short hw_version;
+ unsigned short ll_mtu;
+ spinlock_t lock;
+
+ int initializing;
+ int init_loops;
+ struct timer_list setup_timer;
+
+ /* Flag if hw is ready to send next packet */
+ int tx_ready;
+ /* Count of pending packets to be sent */
+ int tx_queued;
+ struct list_head tx_queue[NL_NUM_OF_PRIORITIES];
+
+ int rx_bytes_queued;
+ struct list_head rx_queue;
+ /* Pool of rx_packet structures that are not currently used. */
+ struct list_head rx_pool;
+ int rx_pool_size;
+ /* True if reception of data is blocked while userspace processes it. */
+ int blocking_rx;
+ /* True if there is RX data ready on the hardware. */
+ int rx_ready;
+ unsigned short last_memtx_serial;
+ /*
+ * Newer versions of the V2 card firmware send serial numbers in the
+ * MemTX register. 'serial_number_detected' is set true when we detect
+ * a non-zero serial number (indicating the new firmware). Thereafter,
+ * the driver can safely ignore the Timer Recovery re-sends to avoid
+ * out-of-sync problems.
+ */
+ int serial_number_detected;
+ struct work_struct work_rx;
+
+ /* True if we are to send the set-up data to the hardware. */
+ int to_setup;
+
+ /* Card has been removed */
+ int removed;
+ /* Saved irq value when we disable the interrupt. */
+ int irq;
+ /* True if this driver is shutting down. */
+ int shutting_down;
+ /* Modem control lines */
+ unsigned int control_lines[NL_NUM_OF_ADDRESSES];
+ struct ipw_rx_packet *packet_assembler[NL_NUM_OF_ADDRESSES];
+
+ struct tasklet_struct tasklet;
+
+ /* The handle for the network layer, for the sending of events to it. */
+ struct ipw_network *network;
+ struct MEMINFREG __iomem *memory_info_regs;
+ struct MEMCCR __iomem *memregs_CCR;
+ void (*reboot_callback) (void *data);
+ void *reboot_callback_data;
+
+ unsigned short __iomem *memreg_tx;
+};
+
+/*
+ * Packet info structure for tx packets.
+ * Note: not all the fields defined here are required for all protocols
+ */
+struct ipw_tx_packet {
+ struct list_head queue;
+ /* channel idx + 1 */
+ unsigned char dest_addr;
+ /* SETUP, CTRL or DATA */
+ unsigned char protocol;
+ /* Length of data block, which starts at the end of this structure */
+ unsigned short length;
+ /* Sending state */
+ /* Offset of where we've sent up to so far */
+ unsigned long offset;
+ /* Count of packet fragments, starting at 0 */
+ int fragment_count;
+
+ /* Called after packet is sent and before is freed */
+ void (*packet_callback) (void *cb_data, unsigned int packet_length);
+ void *callback_data;
+};
+
+/* Signals from DTE */
+#define COMCTRL_RTS 0
+#define COMCTRL_DTR 1
+
+/* Signals from DCE */
+#define COMCTRL_CTS 2
+#define COMCTRL_DCD 3
+#define COMCTRL_DSR 4
+#define COMCTRL_RI 5
+
+struct ipw_control_packet_body {
+ /* DTE signal or DCE signal */
+ unsigned char sig_no;
+ /* 0: set signal, 1: clear signal */
+ unsigned char value;
+} __attribute__ ((__packed__));
+
+struct ipw_control_packet {
+ struct ipw_tx_packet header;
+ struct ipw_control_packet_body body;
+};
+
+struct ipw_rx_packet {
+ struct list_head queue;
+ unsigned int capacity;
+ unsigned int length;
+ unsigned int protocol;
+ unsigned int channel_idx;
+};
+
+static char *data_type(const unsigned char *buf, unsigned length)
+{
+ struct nl_packet_header *hdr = (struct nl_packet_header *) buf;
+
+ if (length == 0)
+ return " ";
+
+ if (hdr->packet_rank & NL_FIRST_PACKET) {
+ switch (hdr->protocol) {
+ case TL_PROTOCOLID_COM_DATA: return "DATA ";
+ case TL_PROTOCOLID_COM_CTRL: return "CTRL ";
+ case TL_PROTOCOLID_SETUP: return "SETUP";
+ default: return "???? ";
+ }
+ } else
+ return " ";
+}
+
+#define DUMP_MAX_BYTES 64
+
+static void dump_data_bytes(const char *type, const unsigned char *data,
+ unsigned length)
+{
+ char prefix[56];
+
+ sprintf(prefix, IPWIRELESS_PCCARD_NAME ": %s %s ",
+ type, data_type(data, length));
+ print_hex_dump_bytes(prefix, 0, (void *)data,
+ length < DUMP_MAX_BYTES ? length : DUMP_MAX_BYTES);
+}
+
+static void swap_packet_bitfield_to_le(unsigned char *data)
+{
+#ifdef __BIG_ENDIAN_BITFIELD
+ unsigned char tmp = *data, ret = 0;
+
+ /*
+ * transform bits from aa.bbb.ccc to ccc.bbb.aa
+ */
+ ret |= tmp & 0xc0 >> 6;
+ ret |= tmp & 0x38 >> 1;
+ ret |= tmp & 0x07 << 5;
+ *data = ret & 0xff;
+#endif
+}
+
+static void swap_packet_bitfield_from_le(unsigned char *data)
+{
+#ifdef __BIG_ENDIAN_BITFIELD
+ unsigned char tmp = *data, ret = 0;
+
+ /*
+ * transform bits from ccc.bbb.aa to aa.bbb.ccc
+ */
+ ret |= tmp & 0xe0 >> 5;
+ ret |= tmp & 0x1c << 1;
+ ret |= tmp & 0x03 << 6;
+ *data = ret & 0xff;
+#endif
+}
+
+static void do_send_fragment(struct ipw_hardware *hw, unsigned char *data,
+ unsigned length)
+{
+ unsigned i;
+ unsigned long flags;
+
+ start_timing();
+ BUG_ON(length > hw->ll_mtu);
+
+ if (ipwireless_debug)
+ dump_data_bytes("send", data, length);
+
+ spin_lock_irqsave(&hw->lock, flags);
+
+ hw->tx_ready = 0;
+ swap_packet_bitfield_to_le(data);
+
+ if (hw->hw_version == HW_VERSION_1) {
+ outw((unsigned short) length, hw->base_port + IODWR);
+
+ for (i = 0; i < length; i += 2) {
+ unsigned short d = data[i];
+ __le16 raw_data;
+
+ if (i + 1 < length)
+ d |= data[i + 1] << 8;
+ raw_data = cpu_to_le16(d);
+ outw(raw_data, hw->base_port + IODWR);
+ }
+
+ outw(DCR_TXDONE, hw->base_port + IODCR);
+ } else if (hw->hw_version == HW_VERSION_2) {
+ outw((unsigned short) length, hw->base_port);
+
+ for (i = 0; i < length; i += 2) {
+ unsigned short d = data[i];
+ __le16 raw_data;
+
+ if (i + 1 < length)
+ d |= data[i + 1] << 8;
+ raw_data = cpu_to_le16(d);
+ outw(raw_data, hw->base_port);
+ }
+ while ((i & 3) != 2) {
+ outw((unsigned short) 0xDEAD, hw->base_port);
+ i += 2;
+ }
+ writew(MEMRX_RX, &hw->memory_info_regs->memreg_rx);
+ }
+
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ end_write_timing(length);
+}
+
+static void do_send_packet(struct ipw_hardware *hw, struct ipw_tx_packet *packet)
+{
+ unsigned short fragment_data_len;
+ unsigned short data_left = packet->length - packet->offset;
+ unsigned short header_size;
+ union nl_packet pkt;
+
+ header_size =
+ (packet->fragment_count == 0)
+ ? NL_FIRST_PACKET_HEADER_SIZE
+ : NL_FOLLOWING_PACKET_HEADER_SIZE;
+ fragment_data_len = hw->ll_mtu - header_size;
+ if (data_left < fragment_data_len)
+ fragment_data_len = data_left;
+
+ /*
+ * hdr_first is now in machine bitfield order, which will be swapped
+ * to le just before it goes to hw
+ */
+ pkt.hdr_first.protocol = packet->protocol;
+ pkt.hdr_first.address = packet->dest_addr;
+ pkt.hdr_first.packet_rank = 0;
+
+ /* First packet? */
+ if (packet->fragment_count == 0) {
+ pkt.hdr_first.packet_rank |= NL_FIRST_PACKET;
+ pkt.hdr_first.length_lsb = (unsigned char) packet->length;
+ pkt.hdr_first.length_msb =
+ (unsigned char) (packet->length >> 8);
+ }
+
+ memcpy(pkt.rawpkt + header_size,
+ ((unsigned char *) packet) + sizeof(struct ipw_tx_packet) +
+ packet->offset, fragment_data_len);
+ packet->offset += fragment_data_len;
+ packet->fragment_count++;
+
+ /* Last packet? (May also be first packet.) */
+ if (packet->offset == packet->length)
+ pkt.hdr_first.packet_rank |= NL_LAST_PACKET;
+ do_send_fragment(hw, pkt.rawpkt, header_size + fragment_data_len);
+
+ /* If this packet has unsent data, then re-queue it. */
+ if (packet->offset < packet->length) {
+ /*
+ * Re-queue it at the head of the highest priority queue so
+ * it goes before all other packets
+ */
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ list_add(&packet->queue, &hw->tx_queue[0]);
+ hw->tx_queued++;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ } else {
+ if (packet->packet_callback)
+ packet->packet_callback(packet->callback_data,
+ packet->length);
+ kfree(packet);
+ }
+}
+
+static void ipw_setup_hardware(struct ipw_hardware *hw)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ if (hw->hw_version == HW_VERSION_1) {
+ /* Reset RX FIFO */
+ outw(DCR_RXRESET, hw->base_port + IODCR);
+ /* SB: Reset TX FIFO */
+ outw(DCR_TXRESET, hw->base_port + IODCR);
+
+ /* Enable TX and RX interrupts. */
+ outw(IER_TXENABLED | IER_RXENABLED, hw->base_port + IOIER);
+ } else {
+ /*
+ * Set INTRACK bit (bit 0), which means we must explicitly
+ * acknowledge interrupts by clearing bit 2 of reg_config_and_status.
+ */
+ unsigned short csr = readw(&hw->memregs_CCR->reg_config_and_status);
+
+ csr |= 1;
+ writew(csr, &hw->memregs_CCR->reg_config_and_status);
+ }
+ spin_unlock_irqrestore(&hw->lock, flags);
+}
+
+/*
+ * If 'packet' is NULL, then this function allocates a new packet, setting its
+ * length to 0 and ensuring it has the specified minimum amount of free space.
+ *
+ * If 'packet' is not NULL, then this function enlarges it if it doesn't
+ * have the specified minimum amount of free space.
+ *
+ */
+static struct ipw_rx_packet *pool_allocate(struct ipw_hardware *hw,
+ struct ipw_rx_packet *packet,
+ int minimum_free_space)
+{
+
+ if (!packet) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ if (!list_empty(&hw->rx_pool)) {
+ packet = list_first_entry(&hw->rx_pool,
+ struct ipw_rx_packet, queue);
+ hw->rx_pool_size--;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ list_del(&packet->queue);
+ } else {
+ const int min_capacity =
+ ipwireless_ppp_mru(hw->network) + 2;
+ int new_capacity;
+
+ spin_unlock_irqrestore(&hw->lock, flags);
+ new_capacity =
+ (minimum_free_space > min_capacity
+ ? minimum_free_space
+ : min_capacity);
+ packet = kmalloc(sizeof(struct ipw_rx_packet)
+ + new_capacity, GFP_ATOMIC);
+ if (!packet)
+ return NULL;
+ packet->capacity = new_capacity;
+ }
+ packet->length = 0;
+ }
+
+ if (packet->length + minimum_free_space > packet->capacity) {
+ struct ipw_rx_packet *old_packet = packet;
+
+ packet = kmalloc(sizeof(struct ipw_rx_packet) +
+ old_packet->length + minimum_free_space,
+ GFP_ATOMIC);
+ if (!packet) {
+ kfree(old_packet);
+ return NULL;
+ }
+ memcpy(packet, old_packet,
+ sizeof(struct ipw_rx_packet)
+ + old_packet->length);
+ packet->capacity = old_packet->length + minimum_free_space;
+ kfree(old_packet);
+ }
+
+ return packet;
+}
+
+static void pool_free(struct ipw_hardware *hw, struct ipw_rx_packet *packet)
+{
+ if (hw->rx_pool_size > 6)
+ kfree(packet);
+ else {
+ hw->rx_pool_size++;
+ list_add(&packet->queue, &hw->rx_pool);
+ }
+}
+
+static void queue_received_packet(struct ipw_hardware *hw,
+ unsigned int protocol,
+ unsigned int address,
+ const unsigned char *data, int length,
+ int is_last)
+{
+ unsigned int channel_idx = address - 1;
+ struct ipw_rx_packet *packet = NULL;
+ unsigned long flags;
+
+ /* Discard packet if channel index is out of range. */
+ if (channel_idx >= NL_NUM_OF_ADDRESSES) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": data packet has bad address %u\n", address);
+ return;
+ }
+
+ /*
+ * ->packet_assembler is safe to touch unlocked, this is the only place
+ */
+ if (protocol == TL_PROTOCOLID_COM_DATA) {
+ struct ipw_rx_packet **assem =
+ &hw->packet_assembler[channel_idx];
+
+ /*
+ * Create a new packet, or assembler already contains one
+ * enlarge it by 'length' bytes.
+ */
+ (*assem) = pool_allocate(hw, *assem, length);
+ if (!(*assem)) {
+ printk(KERN_ERR IPWIRELESS_PCCARD_NAME
+ ": no memory for incomming data packet, dropped!\n");
+ return;
+ }
+ (*assem)->protocol = protocol;
+ (*assem)->channel_idx = channel_idx;
+
+ /* Append this packet data onto existing data. */
+ memcpy((unsigned char *)(*assem) +
+ sizeof(struct ipw_rx_packet)
+ + (*assem)->length, data, length);
+ (*assem)->length += length;
+ if (is_last) {
+ packet = *assem;
+ *assem = NULL;
+ /* Count queued DATA bytes only */
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->rx_bytes_queued += packet->length;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ }
+ } else {
+ /* If it's a CTRL packet, don't assemble, just queue it. */
+ packet = pool_allocate(hw, NULL, length);
+ if (!packet) {
+ printk(KERN_ERR IPWIRELESS_PCCARD_NAME
+ ": no memory for incomming ctrl packet, dropped!\n");
+ return;
+ }
+ packet->protocol = protocol;
+ packet->channel_idx = channel_idx;
+ memcpy((unsigned char *)packet + sizeof(struct ipw_rx_packet),
+ data, length);
+ packet->length = length;
+ }
+
+ /*
+ * If this is the last packet, then send the assembled packet on to the
+ * network layer.
+ */
+ if (packet) {
+ spin_lock_irqsave(&hw->lock, flags);
+ list_add_tail(&packet->queue, &hw->rx_queue);
+ /* Block reception of incoming packets if queue is full. */
+ hw->blocking_rx =
+ (hw->rx_bytes_queued >= IPWIRELESS_RX_QUEUE_SIZE);
+
+ spin_unlock_irqrestore(&hw->lock, flags);
+ schedule_work(&hw->work_rx);
+ }
+}
+
+/*
+ * Workqueue callback
+ */
+static void ipw_receive_data_work(struct work_struct *work_rx)
+{
+ struct ipw_hardware *hw =
+ container_of(work_rx, struct ipw_hardware, work_rx);
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ while (!list_empty(&hw->rx_queue)) {
+ struct ipw_rx_packet *packet =
+ list_first_entry(&hw->rx_queue,
+ struct ipw_rx_packet, queue);
+
+ if (hw->shutting_down)
+ break;
+ list_del(&packet->queue);
+
+ /*
+ * Note: ipwireless_network_packet_received must be called in a
+ * process context (i.e. via schedule_work) because the tty
+ * output code can sleep in the tty_flip_buffer_push call.
+ */
+ if (packet->protocol == TL_PROTOCOLID_COM_DATA) {
+ if (hw->network != NULL) {
+ /* If the network hasn't been disconnected. */
+ spin_unlock_irqrestore(&hw->lock, flags);
+ /*
+ * This must run unlocked due to tty processing
+ * and mutex locking
+ */
+ ipwireless_network_packet_received(
+ hw->network,
+ packet->channel_idx,
+ (unsigned char *)packet
+ + sizeof(struct ipw_rx_packet),
+ packet->length);
+ spin_lock_irqsave(&hw->lock, flags);
+ }
+ /* Count queued DATA bytes only */
+ hw->rx_bytes_queued -= packet->length;
+ } else {
+ /*
+ * This is safe to be called locked, callchain does
+ * not block
+ */
+ handle_received_CTRL_packet(hw, packet->channel_idx,
+ (unsigned char *)packet
+ + sizeof(struct ipw_rx_packet),
+ packet->length);
+ }
+ pool_free(hw, packet);
+ /*
+ * Unblock reception of incoming packets if queue is no longer
+ * full.
+ */
+ hw->blocking_rx =
+ hw->rx_bytes_queued >= IPWIRELESS_RX_QUEUE_SIZE;
+ if (hw->shutting_down)
+ break;
+ }
+ spin_unlock_irqrestore(&hw->lock, flags);
+}
+
+static void handle_received_CTRL_packet(struct ipw_hardware *hw,
+ unsigned int channel_idx,
+ const unsigned char *data, int len)
+{
+ const struct ipw_control_packet_body *body =
+ (const struct ipw_control_packet_body *) data;
+ unsigned int changed_mask;
+
+ if (len != sizeof(struct ipw_control_packet_body)) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": control packet was %d bytes - wrong size!\n",
+ len);
+ return;
+ }
+
+ switch (body->sig_no) {
+ case COMCTRL_CTS:
+ changed_mask = IPW_CONTROL_LINE_CTS;
+ break;
+ case COMCTRL_DCD:
+ changed_mask = IPW_CONTROL_LINE_DCD;
+ break;
+ case COMCTRL_DSR:
+ changed_mask = IPW_CONTROL_LINE_DSR;
+ break;
+ case COMCTRL_RI:
+ changed_mask = IPW_CONTROL_LINE_RI;
+ break;
+ default:
+ changed_mask = 0;
+ }
+
+ if (changed_mask != 0) {
+ if (body->value)
+ hw->control_lines[channel_idx] |= changed_mask;
+ else
+ hw->control_lines[channel_idx] &= ~changed_mask;
+ if (hw->network)
+ ipwireless_network_notify_control_line_change(
+ hw->network,
+ channel_idx,
+ hw->control_lines[channel_idx],
+ changed_mask);
+ }
+}
+
+static void handle_received_packet(struct ipw_hardware *hw,
+ const union nl_packet *packet,
+ unsigned short len)
+{
+ unsigned int protocol = packet->hdr.protocol;
+ unsigned int address = packet->hdr.address;
+ unsigned int header_length;
+ const unsigned char *data;
+ unsigned int data_len;
+ int is_last = packet->hdr.packet_rank & NL_LAST_PACKET;
+
+ if (packet->hdr.packet_rank & NL_FIRST_PACKET)
+ header_length = NL_FIRST_PACKET_HEADER_SIZE;
+ else
+ header_length = NL_FOLLOWING_PACKET_HEADER_SIZE;
+
+ data = packet->rawpkt + header_length;
+ data_len = len - header_length;
+ switch (protocol) {
+ case TL_PROTOCOLID_COM_DATA:
+ case TL_PROTOCOLID_COM_CTRL:
+ queue_received_packet(hw, protocol, address, data, data_len,
+ is_last);
+ break;
+ case TL_PROTOCOLID_SETUP:
+ handle_received_SETUP_packet(hw, address, data, data_len,
+ is_last);
+ break;
+ }
+}
+
+static void acknowledge_data_read(struct ipw_hardware *hw)
+{
+ if (hw->hw_version == HW_VERSION_1)
+ outw(DCR_RXDONE, hw->base_port + IODCR);
+ else
+ writew(MEMRX_PCINTACKK,
+ &hw->memory_info_regs->memreg_pc_interrupt_ack);
+}
+
+/*
+ * Retrieve a packet from the IPW hardware.
+ */
+static void do_receive_packet(struct ipw_hardware *hw)
+{
+ unsigned len;
+ unsigned i;
+ unsigned char pkt[LL_MTU_MAX];
+
+ start_timing();
+
+ if (hw->hw_version == HW_VERSION_1) {
+ len = inw(hw->base_port + IODRR);
+ if (len > hw->ll_mtu) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": received a packet of %u bytes - longer than the MTU!\n", len);
+ outw(DCR_RXDONE | DCR_RXRESET, hw->base_port + IODCR);
+ return;
+ }
+
+ for (i = 0; i < len; i += 2) {
+ __le16 raw_data = inw(hw->base_port + IODRR);
+ unsigned short data = le16_to_cpu(raw_data);
+
+ pkt[i] = (unsigned char) data;
+ pkt[i + 1] = (unsigned char) (data >> 8);
+ }
+ } else {
+ len = inw(hw->base_port);
+ if (len > hw->ll_mtu) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": received a packet of %u bytes - longer than the MTU!\n", len);
+ writew(MEMRX_PCINTACKK,
+ &hw->memory_info_regs->memreg_pc_interrupt_ack);
+ return;
+ }
+
+ for (i = 0; i < len; i += 2) {
+ __le16 raw_data = inw(hw->base_port);
+ unsigned short data = le16_to_cpu(raw_data);
+
+ pkt[i] = (unsigned char) data;
+ pkt[i + 1] = (unsigned char) (data >> 8);
+ }
+
+ while ((i & 3) != 2) {
+ inw(hw->base_port);
+ i += 2;
+ }
+ }
+
+ acknowledge_data_read(hw);
+
+ swap_packet_bitfield_from_le(pkt);
+
+ if (ipwireless_debug)
+ dump_data_bytes("recv", pkt, len);
+
+ handle_received_packet(hw, (union nl_packet *) pkt, len);
+
+ end_read_timing(len);
+}
+
+static int get_current_packet_priority(struct ipw_hardware *hw)
+{
+ /*
+ * If we're initializing, don't send anything of higher priority than
+ * PRIO_SETUP. The network layer therefore need not care about
+ * hardware initialization - any of its stuff will simply be queued
+ * until setup is complete.
+ */
+ return (hw->to_setup || hw->initializing
+ ? PRIO_SETUP + 1 : NL_NUM_OF_PRIORITIES);
+}
+
+/*
+ * return 1 if something has been received from hw
+ */
+static int get_packets_from_hw(struct ipw_hardware *hw)
+{
+ int received = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ while (hw->rx_ready && !hw->blocking_rx) {
+ received = 1;
+ hw->rx_ready--;
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ do_receive_packet(hw);
+
+ spin_lock_irqsave(&hw->lock, flags);
+ }
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ return received;
+}
+
+/*
+ * Send pending packet up to given priority, prioritize SETUP data until
+ * hardware is fully setup.
+ *
+ * return 1 if more packets can be sent
+ */
+static int send_pending_packet(struct ipw_hardware *hw, int priority_limit)
+{
+ int more_to_send = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ if (hw->tx_queued && hw->tx_ready) {
+ int priority;
+ struct ipw_tx_packet *packet = NULL;
+
+ /* Pick a packet */
+ for (priority = 0; priority < priority_limit; priority++) {
+ if (!list_empty(&hw->tx_queue[priority])) {
+ packet = list_first_entry(
+ &hw->tx_queue[priority],
+ struct ipw_tx_packet,
+ queue);
+
+ hw->tx_queued--;
+ list_del(&packet->queue);
+
+ break;
+ }
+ }
+ if (!packet) {
+ hw->tx_queued = 0;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ return 0;
+ }
+
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ /* Send */
+ do_send_packet(hw, packet);
+
+ /* Check if more to send */
+ spin_lock_irqsave(&hw->lock, flags);
+ for (priority = 0; priority < priority_limit; priority++)
+ if (!list_empty(&hw->tx_queue[priority])) {
+ more_to_send = 1;
+ break;
+ }
+
+ if (!more_to_send)
+ hw->tx_queued = 0;
+ }
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ return more_to_send;
+}
+
+/*
+ * Send and receive all queued packets.
+ */
+static void ipwireless_do_tasklet(unsigned long hw_)
+{
+ struct ipw_hardware *hw = (struct ipw_hardware *) hw_;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ if (hw->shutting_down) {
+ spin_unlock_irqrestore(&hw->lock, flags);
+ return;
+ }
+
+ if (hw->to_setup == 1) {
+ /*
+ * Initial setup data sent to hardware
+ */
+ hw->to_setup = 2;
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ ipw_setup_hardware(hw);
+ ipw_send_setup_packet(hw);
+
+ send_pending_packet(hw, PRIO_SETUP + 1);
+ get_packets_from_hw(hw);
+ } else {
+ int priority_limit = get_current_packet_priority(hw);
+ int again;
+
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ do {
+ again = send_pending_packet(hw, priority_limit);
+ again |= get_packets_from_hw(hw);
+ } while (again);
+ }
+}
+
+/*
+ * return true if the card is physically present.
+ */
+static int is_card_present(struct ipw_hardware *hw)
+{
+ if (hw->hw_version == HW_VERSION_1)
+ return inw(hw->base_port + IOIR) != 0xFFFF;
+ else
+ return readl(&hw->memory_info_regs->memreg_card_present) ==
+ CARD_PRESENT_VALUE;
+}
+
+static irqreturn_t ipwireless_handle_v1_interrupt(int irq,
+ struct ipw_hardware *hw)
+{
+ unsigned short irqn;
+
+ irqn = inw(hw->base_port + IOIR);
+
+ /* Check if card is present */
+ if (irqn == 0xFFFF)
+ return IRQ_NONE;
+ else if (irqn != 0) {
+ unsigned short ack = 0;
+ unsigned long flags;
+
+ /* Transmit complete. */
+ if (irqn & IR_TXINTR) {
+ ack |= IR_TXINTR;
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->tx_ready = 1;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ }
+ /* Received data */
+ if (irqn & IR_RXINTR) {
+ ack |= IR_RXINTR;
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->rx_ready++;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ }
+ if (ack != 0) {
+ outw(ack, hw->base_port + IOIR);
+ tasklet_schedule(&hw->tasklet);
+ }
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static void acknowledge_pcmcia_interrupt(struct ipw_hardware *hw)
+{
+ unsigned short csr = readw(&hw->memregs_CCR->reg_config_and_status);
+
+ csr &= 0xfffd;
+ writew(csr, &hw->memregs_CCR->reg_config_and_status);
+}
+
+static irqreturn_t ipwireless_handle_v2_v3_interrupt(int irq,
+ struct ipw_hardware *hw)
+{
+ int tx = 0;
+ int rx = 0;
+ int rx_repeat = 0;
+ int try_mem_tx_old;
+ unsigned long flags;
+
+ do {
+
+ unsigned short memtx = readw(hw->memreg_tx);
+ unsigned short memtx_serial;
+ unsigned short memrxdone =
+ readw(&hw->memory_info_regs->memreg_rx_done);
+
+ try_mem_tx_old = 0;
+
+ /* check whether the interrupt was generated by ipwireless card */
+ if (!(memtx & MEMTX_TX) && !(memrxdone & MEMRX_RX_DONE)) {
+
+ /* check if the card uses memreg_tx_old register */
+ if (hw->memreg_tx == &hw->memory_info_regs->memreg_tx_new) {
+ memtx = readw(&hw->memory_info_regs->memreg_tx_old);
+ if (memtx & MEMTX_TX) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": Using memreg_tx_old\n");
+ hw->memreg_tx =
+ &hw->memory_info_regs->memreg_tx_old;
+ } else {
+ return IRQ_NONE;
+ }
+ } else
+ return IRQ_NONE;
+ }
+
+ /*
+ * See if the card is physically present. Note that while it is
+ * powering up, it appears not to be present.
+ */
+ if (!is_card_present(hw)) {
+ acknowledge_pcmcia_interrupt(hw);
+ return IRQ_HANDLED;
+ }
+
+ memtx_serial = memtx & (unsigned short) 0xff00;
+ if (memtx & MEMTX_TX) {
+ writew(memtx_serial, hw->memreg_tx);
+
+ if (hw->serial_number_detected) {
+ if (memtx_serial != hw->last_memtx_serial) {
+ hw->last_memtx_serial = memtx_serial;
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->rx_ready++;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ rx = 1;
+ } else
+ /* Ignore 'Timer Recovery' duplicates. */
+ rx_repeat = 1;
+ } else {
+ /*
+ * If a non-zero serial number is seen, then enable
+ * serial number checking.
+ */
+ if (memtx_serial != 0) {
+ hw->serial_number_detected = 1;
+ printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
+ ": memreg_tx serial num detected\n");
+
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->rx_ready++;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ }
+ rx = 1;
+ }
+ }
+ if (memrxdone & MEMRX_RX_DONE) {
+ writew(0, &hw->memory_info_regs->memreg_rx_done);
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->tx_ready = 1;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ tx = 1;
+ }
+ if (tx)
+ writew(MEMRX_PCINTACKK,
+ &hw->memory_info_regs->memreg_pc_interrupt_ack);
+
+ acknowledge_pcmcia_interrupt(hw);
+
+ if (tx || rx)
+ tasklet_schedule(&hw->tasklet);
+ else if (!rx_repeat) {
+ if (hw->memreg_tx == &hw->memory_info_regs->memreg_tx_new) {
+ if (hw->serial_number_detected)
+ printk(KERN_WARNING IPWIRELESS_PCCARD_NAME
+ ": spurious interrupt - new_tx mode\n");
+ else {
+ printk(KERN_WARNING IPWIRELESS_PCCARD_NAME
+ ": no valid memreg_tx value - switching to the old memreg_tx\n");
+ hw->memreg_tx =
+ &hw->memory_info_regs->memreg_tx_old;
+ try_mem_tx_old = 1;
+ }
+ } else
+ printk(KERN_WARNING IPWIRELESS_PCCARD_NAME
+ ": spurious interrupt - old_tx mode\n");
+ }
+
+ } while (try_mem_tx_old == 1);
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t ipwireless_interrupt(int irq, void *dev_id)
+{
+ struct ipw_dev *ipw = dev_id;
+
+ if (ipw->hardware->hw_version == HW_VERSION_1)
+ return ipwireless_handle_v1_interrupt(irq, ipw->hardware);
+ else
+ return ipwireless_handle_v2_v3_interrupt(irq, ipw->hardware);
+}
+
+static void flush_packets_to_hw(struct ipw_hardware *hw)
+{
+ int priority_limit;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ priority_limit = get_current_packet_priority(hw);
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ while (send_pending_packet(hw, priority_limit));
+}
+
+static void send_packet(struct ipw_hardware *hw, int priority,
+ struct ipw_tx_packet *packet)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ list_add_tail(&packet->queue, &hw->tx_queue[priority]);
+ hw->tx_queued++;
+ spin_unlock_irqrestore(&hw->lock, flags);
+
+ flush_packets_to_hw(hw);
+}
+
+/* Create data packet, non-atomic allocation */
+static void *alloc_data_packet(int data_size,
+ unsigned char dest_addr,
+ unsigned char protocol)
+{
+ struct ipw_tx_packet *packet = kzalloc(
+ sizeof(struct ipw_tx_packet) + data_size,
+ GFP_ATOMIC);
+
+ if (!packet)
+ return NULL;
+
+ INIT_LIST_HEAD(&packet->queue);
+ packet->dest_addr = dest_addr;
+ packet->protocol = protocol;
+ packet->length = data_size;
+
+ return packet;
+}
+
+static void *alloc_ctrl_packet(int header_size,
+ unsigned char dest_addr,
+ unsigned char protocol,
+ unsigned char sig_no)
+{
+ /*
+ * sig_no is located right after ipw_tx_packet struct in every
+ * CTRL or SETUP packets, we can use ipw_control_packet as a
+ * common struct
+ */
+ struct ipw_control_packet *packet = kzalloc(header_size, GFP_ATOMIC);
+
+ if (!packet)
+ return NULL;
+
+ INIT_LIST_HEAD(&packet->header.queue);
+ packet->header.dest_addr = dest_addr;
+ packet->header.protocol = protocol;
+ packet->header.length = header_size - sizeof(struct ipw_tx_packet);
+ packet->body.sig_no = sig_no;
+
+ return packet;
+}
+
+int ipwireless_send_packet(struct ipw_hardware *hw, unsigned int channel_idx,
+ const unsigned char *data, unsigned int length,
+ void (*callback) (void *cb, unsigned int length),
+ void *callback_data)
+{
+ struct ipw_tx_packet *packet;
+
+ packet = alloc_data_packet(length, (channel_idx + 1),
+ TL_PROTOCOLID_COM_DATA);
+ if (!packet)
+ return -ENOMEM;
+ packet->packet_callback = callback;
+ packet->callback_data = callback_data;
+ memcpy((unsigned char *) packet + sizeof(struct ipw_tx_packet), data,
+ length);
+
+ send_packet(hw, PRIO_DATA, packet);
+ return 0;
+}
+
+static int set_control_line(struct ipw_hardware *hw, int prio,
+ unsigned int channel_idx, int line, int state)
+{
+ struct ipw_control_packet *packet;
+ int protocolid = TL_PROTOCOLID_COM_CTRL;
+
+ if (prio == PRIO_SETUP)
+ protocolid = TL_PROTOCOLID_SETUP;
+
+ packet = alloc_ctrl_packet(sizeof(struct ipw_control_packet),
+ (channel_idx + 1), protocolid, line);
+ if (!packet)
+ return -ENOMEM;
+ packet->header.length = sizeof(struct ipw_control_packet_body);
+ packet->body.value = (state == 0 ? 0 : 1);
+ send_packet(hw, prio, &packet->header);
+ return 0;
+}
+
+
+static int set_DTR(struct ipw_hardware *hw, int priority,
+ unsigned int channel_idx, int state)
+{
+ if (state != 0)
+ hw->control_lines[channel_idx] |= IPW_CONTROL_LINE_DTR;
+ else
+ hw->control_lines[channel_idx] &= ~IPW_CONTROL_LINE_DTR;
+
+ return set_control_line(hw, priority, channel_idx, COMCTRL_DTR, state);
+}
+
+static int set_RTS(struct ipw_hardware *hw, int priority,
+ unsigned int channel_idx, int state)
+{
+ if (state != 0)
+ hw->control_lines[channel_idx] |= IPW_CONTROL_LINE_RTS;
+ else
+ hw->control_lines[channel_idx] &= ~IPW_CONTROL_LINE_RTS;
+
+ return set_control_line(hw, priority, channel_idx, COMCTRL_RTS, state);
+}
+
+int ipwireless_set_DTR(struct ipw_hardware *hw, unsigned int channel_idx,
+ int state)
+{
+ return set_DTR(hw, PRIO_CTRL, channel_idx, state);
+}
+
+int ipwireless_set_RTS(struct ipw_hardware *hw, unsigned int channel_idx,
+ int state)
+{
+ return set_RTS(hw, PRIO_CTRL, channel_idx, state);
+}
+
+struct ipw_setup_get_version_query_packet {
+ struct ipw_tx_packet header;
+ struct tl_setup_get_version_qry body;
+};
+
+struct ipw_setup_config_packet {
+ struct ipw_tx_packet header;
+ struct tl_setup_config_msg body;
+};
+
+struct ipw_setup_config_done_packet {
+ struct ipw_tx_packet header;
+ struct tl_setup_config_done_msg body;
+};
+
+struct ipw_setup_open_packet {
+ struct ipw_tx_packet header;
+ struct tl_setup_open_msg body;
+};
+
+struct ipw_setup_info_packet {
+ struct ipw_tx_packet header;
+ struct tl_setup_info_msg body;
+};
+
+struct ipw_setup_reboot_msg_ack {
+ struct ipw_tx_packet header;
+ struct TlSetupRebootMsgAck body;
+};
+
+/* This handles the actual initialization of the card */
+static void __handle_setup_get_version_rsp(struct ipw_hardware *hw)
+{
+ struct ipw_setup_config_packet *config_packet;
+ struct ipw_setup_config_done_packet *config_done_packet;
+ struct ipw_setup_open_packet *open_packet;
+ struct ipw_setup_info_packet *info_packet;
+ int port;
+ unsigned int channel_idx;
+
+ /* generate config packet */
+ for (port = 1; port <= NL_NUM_OF_ADDRESSES; port++) {
+ config_packet = alloc_ctrl_packet(
+ sizeof(struct ipw_setup_config_packet),
+ ADDR_SETUP_PROT,
+ TL_PROTOCOLID_SETUP,
+ TL_SETUP_SIGNO_CONFIG_MSG);
+ if (!config_packet)
+ goto exit_nomem;
+ config_packet->header.length = sizeof(struct tl_setup_config_msg);
+ config_packet->body.port_no = port;
+ config_packet->body.prio_data = PRIO_DATA;
+ config_packet->body.prio_ctrl = PRIO_CTRL;
+ send_packet(hw, PRIO_SETUP, &config_packet->header);
+ }
+ config_done_packet = alloc_ctrl_packet(
+ sizeof(struct ipw_setup_config_done_packet),
+ ADDR_SETUP_PROT,
+ TL_PROTOCOLID_SETUP,
+ TL_SETUP_SIGNO_CONFIG_DONE_MSG);
+ if (!config_done_packet)
+ goto exit_nomem;
+ config_done_packet->header.length = sizeof(struct tl_setup_config_done_msg);
+ send_packet(hw, PRIO_SETUP, &config_done_packet->header);
+
+ /* generate open packet */
+ for (port = 1; port <= NL_NUM_OF_ADDRESSES; port++) {
+ open_packet = alloc_ctrl_packet(
+ sizeof(struct ipw_setup_open_packet),
+ ADDR_SETUP_PROT,
+ TL_PROTOCOLID_SETUP,
+ TL_SETUP_SIGNO_OPEN_MSG);
+ if (!open_packet)
+ goto exit_nomem;
+ open_packet->header.length = sizeof(struct tl_setup_open_msg);
+ open_packet->body.port_no = port;
+ send_packet(hw, PRIO_SETUP, &open_packet->header);
+ }
+ for (channel_idx = 0;
+ channel_idx < NL_NUM_OF_ADDRESSES; channel_idx++) {
+ int ret;
+
+ ret = set_DTR(hw, PRIO_SETUP, channel_idx,
+ (hw->control_lines[channel_idx] &
+ IPW_CONTROL_LINE_DTR) != 0);
+ if (ret) {
+ printk(KERN_ERR IPWIRELESS_PCCARD_NAME
+ ": error setting DTR (%d)\n", ret);
+ return;
+ }
+
+ set_RTS(hw, PRIO_SETUP, channel_idx,
+ (hw->control_lines [channel_idx] &
+ IPW_CONTROL_LINE_RTS) != 0);
+ if (ret) {
+ printk(KERN_ERR IPWIRELESS_PCCARD_NAME
+ ": error setting RTS (%d)\n", ret);
+ return;
+ }
+ }
+ /*
+ * For NDIS we assume that we are using sync PPP frames, for COM async.
+ * This driver uses NDIS mode too. We don't bother with translation
+ * from async -> sync PPP.
+ */
+ info_packet = alloc_ctrl_packet(sizeof(struct ipw_setup_info_packet),
+ ADDR_SETUP_PROT,
+ TL_PROTOCOLID_SETUP,
+ TL_SETUP_SIGNO_INFO_MSG);
+ if (!info_packet)
+ goto exit_nomem;
+ info_packet->header.length = sizeof(struct tl_setup_info_msg);
+ info_packet->body.driver_type = NDISWAN_DRIVER;
+ info_packet->body.major_version = NDISWAN_DRIVER_MAJOR_VERSION;
+ info_packet->body.minor_version = NDISWAN_DRIVER_MINOR_VERSION;
+ send_packet(hw, PRIO_SETUP, &info_packet->header);
+
+ /* Initialization is now complete, so we clear the 'to_setup' flag */
+ hw->to_setup = 0;
+
+ return;
+
+exit_nomem:
+ printk(KERN_ERR IPWIRELESS_PCCARD_NAME
+ ": not enough memory to alloc control packet\n");
+ hw->to_setup = -1;
+}
+
+static void handle_setup_get_version_rsp(struct ipw_hardware *hw,
+ unsigned char vers_no)
+{
+ del_timer(&hw->setup_timer);
+ hw->initializing = 0;
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": card is ready.\n");
+
+ if (vers_no == TL_SETUP_VERSION)
+ __handle_setup_get_version_rsp(hw);
+ else
+ printk(KERN_ERR IPWIRELESS_PCCARD_NAME
+ ": invalid hardware version no %u\n",
+ (unsigned int) vers_no);
+}
+
+static void ipw_send_setup_packet(struct ipw_hardware *hw)
+{
+ struct ipw_setup_get_version_query_packet *ver_packet;
+
+ ver_packet = alloc_ctrl_packet(
+ sizeof(struct ipw_setup_get_version_query_packet),
+ ADDR_SETUP_PROT, TL_PROTOCOLID_SETUP,
+ TL_SETUP_SIGNO_GET_VERSION_QRY);
+ ver_packet->header.length = sizeof(struct tl_setup_get_version_qry);
+
+ /*
+ * Response is handled in handle_received_SETUP_packet
+ */
+ send_packet(hw, PRIO_SETUP, &ver_packet->header);
+}
+
+static void handle_received_SETUP_packet(struct ipw_hardware *hw,
+ unsigned int address,
+ const unsigned char *data, int len,
+ int is_last)
+{
+ const union ipw_setup_rx_msg *rx_msg = (const union ipw_setup_rx_msg *) data;
+
+ if (address != ADDR_SETUP_PROT) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": setup packet has bad address %d\n", address);
+ return;
+ }
+
+ switch (rx_msg->sig_no) {
+ case TL_SETUP_SIGNO_GET_VERSION_RSP:
+ if (hw->to_setup)
+ handle_setup_get_version_rsp(hw,
+ rx_msg->version_rsp_msg.version);
+ break;
+
+ case TL_SETUP_SIGNO_OPEN_MSG:
+ if (ipwireless_debug) {
+ unsigned int channel_idx = rx_msg->open_msg.port_no - 1;
+
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": OPEN_MSG [channel %u] reply received\n",
+ channel_idx);
+ }
+ break;
+
+ case TL_SETUP_SIGNO_INFO_MSG_ACK:
+ if (ipwireless_debug)
+ printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
+ ": card successfully configured as NDISWAN\n");
+ break;
+
+ case TL_SETUP_SIGNO_REBOOT_MSG:
+ if (hw->to_setup)
+ printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
+ ": Setup not completed - ignoring reboot msg\n");
+ else {
+ struct ipw_setup_reboot_msg_ack *packet;
+
+ printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
+ ": Acknowledging REBOOT message\n");
+ packet = alloc_ctrl_packet(
+ sizeof(struct ipw_setup_reboot_msg_ack),
+ ADDR_SETUP_PROT, TL_PROTOCOLID_SETUP,
+ TL_SETUP_SIGNO_REBOOT_MSG_ACK);
+ packet->header.length =
+ sizeof(struct TlSetupRebootMsgAck);
+ send_packet(hw, PRIO_SETUP, &packet->header);
+ if (hw->reboot_callback)
+ hw->reboot_callback(hw->reboot_callback_data);
+ }
+ break;
+
+ default:
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": unknown setup message %u received\n",
+ (unsigned int) rx_msg->sig_no);
+ }
+}
+
+static void do_close_hardware(struct ipw_hardware *hw)
+{
+ unsigned int irqn;
+
+ if (hw->hw_version == HW_VERSION_1) {
+ /* Disable TX and RX interrupts. */
+ outw(0, hw->base_port + IOIER);
+
+ /* Acknowledge any outstanding interrupt requests */
+ irqn = inw(hw->base_port + IOIR);
+ if (irqn & IR_TXINTR)
+ outw(IR_TXINTR, hw->base_port + IOIR);
+ if (irqn & IR_RXINTR)
+ outw(IR_RXINTR, hw->base_port + IOIR);
+
+ synchronize_irq(hw->irq);
+ }
+}
+
+struct ipw_hardware *ipwireless_hardware_create(void)
+{
+ int i;
+ struct ipw_hardware *hw =
+ kzalloc(sizeof(struct ipw_hardware), GFP_KERNEL);
+
+ if (!hw)
+ return NULL;
+
+ hw->irq = -1;
+ hw->initializing = 1;
+ hw->tx_ready = 1;
+ hw->rx_bytes_queued = 0;
+ hw->rx_pool_size = 0;
+ hw->last_memtx_serial = (unsigned short) 0xffff;
+ for (i = 0; i < NL_NUM_OF_PRIORITIES; i++)
+ INIT_LIST_HEAD(&hw->tx_queue[i]);
+
+ INIT_LIST_HEAD(&hw->rx_queue);
+ INIT_LIST_HEAD(&hw->rx_pool);
+ spin_lock_init(&hw->lock);
+ tasklet_init(&hw->tasklet, ipwireless_do_tasklet, (unsigned long) hw);
+ INIT_WORK(&hw->work_rx, ipw_receive_data_work);
+ setup_timer(&hw->setup_timer, ipwireless_setup_timer,
+ (unsigned long) hw);
+
+ return hw;
+}
+
+void ipwireless_init_hardware_v1(struct ipw_hardware *hw,
+ unsigned int base_port,
+ void __iomem *attr_memory,
+ void __iomem *common_memory,
+ int is_v2_card,
+ void (*reboot_callback) (void *data),
+ void *reboot_callback_data)
+{
+ if (hw->removed) {
+ hw->removed = 0;
+ enable_irq(hw->irq);
+ }
+ hw->base_port = base_port;
+ hw->hw_version = (is_v2_card ? HW_VERSION_2 : HW_VERSION_1);
+ hw->ll_mtu = (hw->hw_version == HW_VERSION_1 ? LL_MTU_V1 : LL_MTU_V2);
+ hw->memregs_CCR = (struct MEMCCR __iomem *)
+ ((unsigned short __iomem *) attr_memory + 0x200);
+ hw->memory_info_regs = (struct MEMINFREG __iomem *) common_memory;
+ hw->memreg_tx = &hw->memory_info_regs->memreg_tx_new;
+ hw->reboot_callback = reboot_callback;
+ hw->reboot_callback_data = reboot_callback_data;
+}
+
+void ipwireless_init_hardware_v2_v3(struct ipw_hardware *hw)
+{
+ hw->initializing = 1;
+ hw->init_loops = 0;
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": waiting for card to start up...\n");
+ ipwireless_setup_timer((unsigned long) hw);
+}
+
+static void ipwireless_setup_timer(unsigned long data)
+{
+ struct ipw_hardware *hw = (struct ipw_hardware *) data;
+
+ hw->init_loops++;
+
+ if (hw->init_loops == TL_SETUP_MAX_VERSION_QRY &&
+ hw->hw_version == HW_VERSION_2 &&
+ hw->memreg_tx == &hw->memory_info_regs->memreg_tx_new) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": failed to startup using TX2, trying TX\n");
+
+ hw->memreg_tx = &hw->memory_info_regs->memreg_tx_old;
+ hw->init_loops = 0;
+ }
+ /* Give up after a certain number of retries */
+ if (hw->init_loops == TL_SETUP_MAX_VERSION_QRY) {
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": card failed to start up!\n");
+ hw->initializing = 0;
+ } else {
+ /* Do not attempt to write to the board if it is not present. */
+ if (is_card_present(hw)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hw->lock, flags);
+ hw->to_setup = 1;
+ hw->tx_ready = 1;
+ spin_unlock_irqrestore(&hw->lock, flags);
+ tasklet_schedule(&hw->tasklet);
+ }
+
+ mod_timer(&hw->setup_timer,
+ jiffies + msecs_to_jiffies(TL_SETUP_VERSION_QRY_TMO));
+ }
+}
+
+/*
+ * Stop any interrupts from executing so that, once this function returns,
+ * other layers of the driver can be sure they won't get any more callbacks.
+ * Thus must be called on a proper process context.
+ */
+void ipwireless_stop_interrupts(struct ipw_hardware *hw)
+{
+ if (!hw->shutting_down) {
+ /* Tell everyone we are going down. */
+ hw->shutting_down = 1;
+ del_timer(&hw->setup_timer);
+
+ /* Prevent the hardware from sending any more interrupts */
+ do_close_hardware(hw);
+ }
+}
+
+void ipwireless_hardware_free(struct ipw_hardware *hw)
+{
+ int i;
+ struct ipw_rx_packet *rp, *rq;
+ struct ipw_tx_packet *tp, *tq;
+
+ ipwireless_stop_interrupts(hw);
+
+ flush_work_sync(&hw->work_rx);
+
+ for (i = 0; i < NL_NUM_OF_ADDRESSES; i++)
+ if (hw->packet_assembler[i] != NULL)
+ kfree(hw->packet_assembler[i]);
+
+ for (i = 0; i < NL_NUM_OF_PRIORITIES; i++)
+ list_for_each_entry_safe(tp, tq, &hw->tx_queue[i], queue) {
+ list_del(&tp->queue);
+ kfree(tp);
+ }
+
+ list_for_each_entry_safe(rp, rq, &hw->rx_queue, queue) {
+ list_del(&rp->queue);
+ kfree(rp);
+ }
+
+ list_for_each_entry_safe(rp, rq, &hw->rx_pool, queue) {
+ list_del(&rp->queue);
+ kfree(rp);
+ }
+ kfree(hw);
+}
+
+/*
+ * Associate the specified network with this hardware, so it will receive events
+ * from it.
+ */
+void ipwireless_associate_network(struct ipw_hardware *hw,
+ struct ipw_network *network)
+{
+ hw->network = network;
+}
diff --git a/drivers/tty/ipwireless/hardware.h b/drivers/tty/ipwireless/hardware.h
new file mode 100644
index 00000000000..90a8590e43b
--- /dev/null
+++ b/drivers/tty/ipwireless/hardware.h
@@ -0,0 +1,62 @@
+/*
+ * IPWireless 3G PCMCIA Network Driver
+ *
+ * Original code
+ * by Stephen Blackheath <stephen@blacksapphire.com>,
+ * Ben Martel <benm@symmetric.co.nz>
+ *
+ * Copyrighted as follows:
+ * Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
+ *
+ * Various driver changes and rewrites, port to new kernels
+ * Copyright (C) 2006-2007 Jiri Kosina
+ *
+ * Misc code cleanups and updates
+ * Copyright (C) 2007 David Sterba
+ */
+
+#ifndef _IPWIRELESS_CS_HARDWARE_H_
+#define _IPWIRELESS_CS_HARDWARE_H_
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+
+#define IPW_CONTROL_LINE_CTS 0x0001
+#define IPW_CONTROL_LINE_DCD 0x0002
+#define IPW_CONTROL_LINE_DSR 0x0004
+#define IPW_CONTROL_LINE_RI 0x0008
+#define IPW_CONTROL_LINE_DTR 0x0010
+#define IPW_CONTROL_LINE_RTS 0x0020
+
+struct ipw_hardware;
+struct ipw_network;
+
+struct ipw_hardware *ipwireless_hardware_create(void);
+void ipwireless_hardware_free(struct ipw_hardware *hw);
+irqreturn_t ipwireless_interrupt(int irq, void *dev_id);
+int ipwireless_set_DTR(struct ipw_hardware *hw, unsigned int channel_idx,
+ int state);
+int ipwireless_set_RTS(struct ipw_hardware *hw, unsigned int channel_idx,
+ int state);
+int ipwireless_send_packet(struct ipw_hardware *hw,
+ unsigned int channel_idx,
+ const unsigned char *data,
+ unsigned int length,
+ void (*packet_sent_callback) (void *cb,
+ unsigned int length),
+ void *sent_cb_data);
+void ipwireless_associate_network(struct ipw_hardware *hw,
+ struct ipw_network *net);
+void ipwireless_stop_interrupts(struct ipw_hardware *hw);
+void ipwireless_init_hardware_v1(struct ipw_hardware *hw,
+ unsigned int base_port,
+ void __iomem *attr_memory,
+ void __iomem *common_memory,
+ int is_v2_card,
+ void (*reboot_cb) (void *data),
+ void *reboot_cb_data);
+void ipwireless_init_hardware_v2_v3(struct ipw_hardware *hw);
+void ipwireless_sleep(unsigned int tenths);
+
+#endif
diff --git a/drivers/tty/ipwireless/main.c b/drivers/tty/ipwireless/main.c
new file mode 100644
index 00000000000..655c7948261
--- /dev/null
+++ b/drivers/tty/ipwireless/main.c
@@ -0,0 +1,347 @@
+/*
+ * IPWireless 3G PCMCIA Network Driver
+ *
+ * Original code
+ * by Stephen Blackheath <stephen@blacksapphire.com>,
+ * Ben Martel <benm@symmetric.co.nz>
+ *
+ * Copyrighted as follows:
+ * Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
+ *
+ * Various driver changes and rewrites, port to new kernels
+ * Copyright (C) 2006-2007 Jiri Kosina
+ *
+ * Misc code cleanups and updates
+ * Copyright (C) 2007 David Sterba
+ */
+
+#include "hardware.h"
+#include "network.h"
+#include "main.h"
+#include "tty.h"
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <pcmcia/cisreg.h>
+#include <pcmcia/device_id.h>
+#include <pcmcia/ss.h>
+#include <pcmcia/ds.h>
+
+static const struct pcmcia_device_id ipw_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0100),
+ PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0200),
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, ipw_ids);
+
+static void ipwireless_detach(struct pcmcia_device *link);
+
+/*
+ * Module params
+ */
+/* Debug mode: more verbose, print sent/recv bytes */
+int ipwireless_debug;
+int ipwireless_loopback;
+int ipwireless_out_queue = 10;
+
+module_param_named(debug, ipwireless_debug, int, 0);
+module_param_named(loopback, ipwireless_loopback, int, 0);
+module_param_named(out_queue, ipwireless_out_queue, int, 0);
+MODULE_PARM_DESC(debug, "switch on debug messages [0]");
+MODULE_PARM_DESC(loopback,
+ "debug: enable ras_raw channel [0]");
+MODULE_PARM_DESC(out_queue, "debug: set size of outgoing PPP queue [10]");
+
+/* Executes in process context. */
+static void signalled_reboot_work(struct work_struct *work_reboot)
+{
+ struct ipw_dev *ipw = container_of(work_reboot, struct ipw_dev,
+ work_reboot);
+ struct pcmcia_device *link = ipw->link;
+ pcmcia_reset_card(link->socket);
+}
+
+static void signalled_reboot_callback(void *callback_data)
+{
+ struct ipw_dev *ipw = (struct ipw_dev *) callback_data;
+
+ /* Delegate to process context. */
+ schedule_work(&ipw->work_reboot);
+}
+
+static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data)
+{
+ struct ipw_dev *ipw = priv_data;
+ int ret;
+
+ p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+ p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+
+ /* 0x40 causes it to generate level mode interrupts. */
+ /* 0x04 enables IREQ pin. */
+ p_dev->config_index |= 0x44;
+ p_dev->io_lines = 16;
+ ret = pcmcia_request_io(p_dev);
+ if (ret)
+ return ret;
+
+ if (!request_region(p_dev->resource[0]->start,
+ resource_size(p_dev->resource[0]),
+ IPWIRELESS_PCCARD_NAME)) {
+ ret = -EBUSY;
+ goto exit;
+ }
+
+ p_dev->resource[2]->flags |=
+ WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE;
+
+ ret = pcmcia_request_window(p_dev, p_dev->resource[2], 0);
+ if (ret != 0)
+ goto exit1;
+
+ ret = pcmcia_map_mem_page(p_dev, p_dev->resource[2], p_dev->card_addr);
+ if (ret != 0)
+ goto exit1;
+
+ ipw->is_v2_card = resource_size(p_dev->resource[2]) == 0x100;
+
+ ipw->common_memory = ioremap(p_dev->resource[2]->start,
+ resource_size(p_dev->resource[2]));
+ if (!request_mem_region(p_dev->resource[2]->start,
+ resource_size(p_dev->resource[2]),
+ IPWIRELESS_PCCARD_NAME)) {
+ ret = -EBUSY;
+ goto exit2;
+ }
+
+ p_dev->resource[3]->flags |= WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_AM |
+ WIN_ENABLE;
+ p_dev->resource[3]->end = 0; /* this used to be 0x1000 */
+ ret = pcmcia_request_window(p_dev, p_dev->resource[3], 0);
+ if (ret != 0)
+ goto exit3;
+
+ ret = pcmcia_map_mem_page(p_dev, p_dev->resource[3], 0);
+ if (ret != 0)
+ goto exit3;
+
+ ipw->attr_memory = ioremap(p_dev->resource[3]->start,
+ resource_size(p_dev->resource[3]));
+ if (!request_mem_region(p_dev->resource[3]->start,
+ resource_size(p_dev->resource[3]),
+ IPWIRELESS_PCCARD_NAME)) {
+ ret = -EBUSY;
+ goto exit4;
+ }
+
+ return 0;
+
+exit4:
+ iounmap(ipw->attr_memory);
+exit3:
+ release_mem_region(p_dev->resource[2]->start,
+ resource_size(p_dev->resource[2]));
+exit2:
+ iounmap(ipw->common_memory);
+exit1:
+ release_region(p_dev->resource[0]->start,
+ resource_size(p_dev->resource[0]));
+exit:
+ pcmcia_disable_device(p_dev);
+ return ret;
+}
+
+static int config_ipwireless(struct ipw_dev *ipw)
+{
+ struct pcmcia_device *link = ipw->link;
+ int ret = 0;
+
+ ipw->is_v2_card = 0;
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_AUTO_SET_IOMEM |
+ CONF_ENABLE_IRQ;
+
+ ret = pcmcia_loop_config(link, ipwireless_probe, ipw);
+ if (ret != 0)
+ return ret;
+
+ INIT_WORK(&ipw->work_reboot, signalled_reboot_work);
+
+ ipwireless_init_hardware_v1(ipw->hardware, link->resource[0]->start,
+ ipw->attr_memory, ipw->common_memory,
+ ipw->is_v2_card, signalled_reboot_callback,
+ ipw);
+
+ ret = pcmcia_request_irq(link, ipwireless_interrupt);
+ if (ret != 0)
+ goto exit;
+
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": Card type %s\n",
+ ipw->is_v2_card ? "V2/V3" : "V1");
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": I/O ports %pR, irq %d\n", link->resource[0],
+ (unsigned int) link->irq);
+ if (ipw->attr_memory && ipw->common_memory)
+ printk(KERN_INFO IPWIRELESS_PCCARD_NAME
+ ": attr memory %pR, common memory %pR\n",
+ link->resource[3],
+ link->resource[2]);
+
+ ipw->network = ipwireless_network_create(ipw->hardware);
+ if (!ipw->network)
+ goto exit;
+
+ ipw->tty = ipwireless_tty_create(ipw->hardware, ipw->network);
+ if (!ipw->tty)
+ goto exit;
+
+ ipwireless_init_hardware_v2_v3(ipw->hardware);
+
+ /*
+ * Do the RequestConfiguration last, because it enables interrupts.
+ * Then we don't get any interrupts before we're ready for them.
+ */
+ ret = pcmcia_enable_device(link);
+ if (ret != 0)
+ goto exit;
+
+ return 0;
+
+exit:
+ if (ipw->common_memory) {
+ release_mem_region(link->resource[2]->start,
+ resource_size(link->resource[2]));
+ iounmap(ipw->common_memory);
+ }
+ if (ipw->attr_memory) {
+ release_mem_region(link->resource[3]->start,
+ resource_size(link->resource[3]));
+ iounmap(ipw->attr_memory);
+ }
+ pcmcia_disable_device(link);
+ return -1;
+}
+
+static void release_ipwireless(struct ipw_dev *ipw)
+{
+ release_region(ipw->link->resource[0]->start,
+ resource_size(ipw->link->resource[0]));
+ if (ipw->common_memory) {
+ release_mem_region(ipw->link->resource[2]->start,
+ resource_size(ipw->link->resource[2]));
+ iounmap(ipw->common_memory);
+ }
+ if (ipw->attr_memory) {
+ release_mem_region(ipw->link->resource[3]->start,
+ resource_size(ipw->link->resource[3]));
+ iounmap(ipw->attr_memory);
+ }
+ pcmcia_disable_device(ipw->link);
+}
+
+/*
+ * ipwireless_attach() creates an "instance" of the driver, allocating
+ * local data structures for one device (one interface). The device
+ * is registered with Card Services.
+ *
+ * The pcmcia_device structure is initialized, but we don't actually