- Debugging on Linux for 390
+ Debugging on Linux for s/390 & zSeries
by
Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
- Copyright (C) 2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ Copyright (C) 2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
Best viewed with fixed width fonts
Overview of Document:
=====================
This document is intended to give an good overview of how to debug
-Linux for S390 it isn't intended as a complete reference & not a
+Linux for s/390 & zSeries it isn't intended as a complete reference & not a
tutorial on the fundamentals of C & assembly, it dosen't go into
390 IO in any detail. It is intended to compliment the following
books.
+z/Architecture Principles of Operation SA22-7832-00
Enterprise Systems Architecture/390 Reference Summary SA22-7209-01 & the
Enterprise Systems Architecture/390 Principles of Operation SA22-7201-05
+Linux for zSeries and S/390 Elf Application Binary Interface
& any other worthwhile references you get.
It is intended like the Enterprise Systems Architecture/390 Reference Summary
Contents
========
-S390 Register Set
+Register Set
Address Spaces on Linux
-Address Spaces on Linux for S390
-The Linux for S390 Kernel Task Structure
-Register Usage & Stackframes on Linux for S390 with glossary
-Compiling programs for debugging on Linux for S390
+Address Spaces on Linux for s/390 & zSeries
+The Linux for s/390 & zSeries Kernel Task Structure
+Register Usage & Stackframes on Linux for s/390 & zSeries with glossary
+Compiling programs for debugging on Linux for s/390 & zSeries
Figuring out gcc compile errors
Debugging Tools
objdump
strace
Debugging under VM
Stack Tracing under VM
-S390 IO Overview
-Debugging IO on S390 under VM
-GDB on S390
+s/390 & zSeries IO Overview
+Debugging IO on s/390 & zSeries under VM
+GDB on s/390 & zSeries
Stack chaining in gdb by hand
Examining core dumps
LDD
The proc file system
Starting points for debugging scripting languages etc.
-S390 Register Set
-================
-The current ESA 390 architecture has the following registers.
+Register Set
+============
+The current architectures have the following registers.
-16 32 bit General propose registers ( r0-r15 or gpr0-gpr15) used for arithmetic & addressing
+16 General propose registers, 32 bit on s/390 64 bit on zSeries, r0-r15 or gpr0-gpr15 used for arithmetic & addressing.
-16 Control registers ( cr0-cr15 kernel usage only ) used for memory managment,
+16 Control registers, 32 bit on s/390 64 bit on zSeries, ( cr0-cr15 kernel usage only ) used for memory managment,
interrupt control,debugging control etc.
16 Access registers ( ar0-ar15 ) not used by normal programs but potentially could
The PSW is the most important register on the machine it
-is 64 bit & serves the roles of a program counter (pc),
-condition code register,memory space designator.
+is 64 bit on s/390 & 128 bit on zSeries & serves the roles of
+a program counter (pc), condition code register,memory space designator.
In IBM standard notation I am counting bit 0 as the MSB.
It has several advantages over a normal program counter
in that you can change address translation & program counter
have a logical=physical mapping for the address you are
currently running at.
-Bit Value
+ Bit Value
+s/390 zSeries
+0 0 Reserved ( must be 0 ) otherwise specification exception occurs.
+
+1 1 Program Event Recording 1 PER enabled,
+ PER is used to facilititate debugging e.g. single stepping.
-0 Reserved ( must be 0 ) otherwise specification exception occurs.
+2-4 2-4 Reserved ( must be 0 ).
-1 Program Event Recording 1 PER enabled,
- PER is used to facilititate debugging e.g. single stepping.
+5 5 Dynamic address translation 1=DAT on.
-2-4 Reserved ( must be 0 ).
+6 6 Input/Output interrupt Mask
-5 Dynamic address translation 1=DAT on.
+7 7 External interrupt Mask used primarily for interprocessor signalling &
+ clock interupts.
-6 Input/Output interrupt Mask
+8-11 8-11 PSW Key used for complex memory protection mechanism not used under linux
-7 External interrupt Mask used primarily for interprocessor signalling &
- clock interupts.
+12 12 1 on s/390 0 on zSeries
-8-12 PSW Key used for complex memory protection mechanism not used under linux
+13 13 Machine Check Mask 1=enable machine check interrupts
-13 Machine Check Mask 1=enable machine check interrupts
+14 14 Wait State set this to 1 to stop the processor except for interrupts & give
+ time to other LPARS used in CPU idle in the kernel to increase overall
+ usage of processor resources.
-14 Wait State set this to 1 to stop the processor except for interrupts & give
- time to other LPARS used in CPU idle in the kernel to increase overall
- usage of processor resources.
+15 15 Problem state ( if set to 1 certain instructions are disabled )
+ all linux user programs run with this bit 1
+ ( useful info for debugging under VM ).
-15 Problem state ( if set to 1 certain instructions are disabled )
- all linux user programs run with this bit 1 ( useful info for debugging under VM ).
+16-17 16-17 Address Space Control
-16-17 Address Space Control
+ 00 Primary Space Mode when DAT on
+ The linux kernel currently runs in this mode, CR1 is affiliated with
+ this mode & points to the primary segment table origin etc.
- 00 Primary Space Mode when DAT on
- The linux kernel currently runs in this mode, CR1 is affiliated with this mode
- & points to the primary segment table origin etc.
+ 01 Access register mode this mode is used in functions to
+ copy data between kernel & user space.
- 01 Access register mode this mode is used in functions to
- copy data between kernel & user space.
+ 10 Secondary space mode not used in linux however CR7 the
+ register affiliated with this mode is & this & normally
+ CR13=CR7 to allow us to copy data between kernel & user space.
+ We do this as follows:
+ We set ar2 to 0 to designate its
+ affiliated gpr ( gpr2 )to point to primary=kernel space.
+ We set ar4 to 1 to designate its
+ affiliated gpr ( gpr4 ) to point to secondary=home=user space
+ & then essentially do a memcopy(gpr2,gpr4,size) to
+ copy data between the address spaces, the reason we use home space for the
+ kernel & don't keep secondary space free is that code will not run in
+ secondary space.
- 10 Secondary space mode not used in linux however CR7 the
- register affiliated with this mode is & this & normally
- CR13=CR7 to allow us to copy data between kernel & user space.
- We do this as follows:
- We set ar2 to 0 to designate its
- affiliated gpr ( gpr2 )to point to primary=kernel space.
- We set ar4 to 1 to designate its
- affiliated gpr ( gpr4 ) to point to secondary=home=user space
- & then essentially do a memcopy(gpr2,gpr4,size) to
- copy data between the address spaces, the reason we use home space for the
- kernel & don't keep secondary space free is that code will not run in secondary space.
+ 11 Home Space Mode all user programs run in this mode.
+ it is affiliated with CR13.
- 11 Home Space Mode all user programs run in this mode.
- it is affiliated with CR13.
+18-19 18-19 Condition codes (CC)
-18-19 Condition codes (CC)
+20 20 Fixed point overflow mask if 1=FPU exceptions for this event
+ occur ( normally 0 )
-20 Fixed point overflow mask if 1=FPU exceptions for this event occur ( normally 0 )
+21 21 Decimal overflow mask if 1=FPU exceptions for this event occur
+ ( normally 0 )
-21 Decimal overflow mask if 1=FPU exceptions for this event occur ( normally 0 )
+22 22 Exponent underflow mask if 1=FPU exceptions for this event occur
+ ( normally 0 )
-22 Exponent underflow mask if 1=FPU exceptions for this event occur ( normally 0 )
+23 23 Significance Mask if 1=FPU exceptions for this event occur
+ ( normally 0 )
-23 Significance Mask if 1=FPU exceptions for this event occur ( normally 0 )
+24-31 24-30 Reserved Must be 0.
-24-31 Reserved Must be 0.
+ 31 Extended Addressing Mode
+ 32 Basic Addressing Mode
+ Used to set addressing mode
+ PSW 31 PSW 32
+ 0 0 24 bit
+ 0 1 31 bit
+ 1 1 64 bit
-32 1=31 bit addressing mode 0=24 bit addressing mode (for backward compatibility ),
- linux always runs with this bit set to 1
+32 1=31 bit addressing mode 0=24 bit addressing mode (for backward
+ compatibility ), linux always runs with this bit set to 1
-33-64 Instruction address.
+33-64 Instruction address.
+ 33-63 Reserved must be 0
+ 64-127 Address
+ In 24 bits mode bits 64-103=0 bits 104-127 Address
+ In 31 bits mode bits 64-96=0 bits 97-127 Address
+ Note: unlike 31 bit mode on s/390 bit 96 must be zero
+ when loading the address with LPSWE otherwise a
+ specification exception occurs, LPSW is fully backward
+ compatible.
+
Prefix Page
-----------
Our addressing scheme is as follows
-Himem 0x7fffffff 2GB ***************** ****************
- * User Stack * * *
+Himem 0x7fffffff 2GB on s/390 ***************** ****************
+2^64 bytes on zSeries * User Stack * * *
***************** * *
* Shared Libs * * *
***************** * *
The kernel stack pointer is intimately tied with the task stucture for
each processor as follows.
+ s/390
************************
* 1 page kernel stack *
* ( 4K ) *
* ( 4K ) *
8K aligned ************************
+ zSeries
+ ************************
+ * 2 page kernel stack *
+ * ( 8K ) *
+ ************************
+ * 2 page task_struct *
+ * ( 8K ) *
+16K aligned ************************
+
What this means is that we don't need to dedicate any register or global variable
to point to the current running process & can retrieve it with the following
-very simple construct
+very simple construct for s/390 & one very similar for zSeries.
static inline struct task_struct * get_current(void)
{
Note: To follow stackframes requires a knowledge of C or Pascal &
limited knowledge of one assembly language.
+It should be noted that there are some differences between the
+s/390 & zSeries stack layouts as the zSeries stack layout didn't have
+to maintain compatibility with older linkage formats.
+
Glossary:
---------
alloca:
f0 argument 0 / return value ( float/double ) call-clobbered
f2 argument 1 call-clobbered
-f4 saved
-f6 saved
+f4 zSeries argument 2 saved
+f6 zSeries argument 3 saved
The remaining floating points
f1,f3,f5 f7-f15 are call-clobbered.
area if crossing this boundary.
6) Floating point parameters are mixed with outgoing args
on the outgoing args area in the order the are passed in as parameters.
+7) Floating point arguments 2 & 3 are saved in the outgoing args area for zSeries
+
Stack Frame Layout
==================
-0 back chain ( a 0 here signifies end of back chain )
-4 eos ( end of stack, not used on Linux for S390 used in other linkage formats )
-8 glue used in other linkage formats for saved routine descriptors etc.
-12 glue used in other linkage formats for saved routine descriptors etc.
-16 scratch area
-20 scratch area
-24 saved r6 of caller function
-28 saved r7 of caller function
-32 saved r8 of caller function
-36 saved r9 of caller function
-40 saved r10 of caller function
-44 saved r11 of caller function
-48 saved r12 of caller function
-52 saved r13 of caller function
-56 saved r14 of caller function
-60 saved r15 of caller function
-64 saved f4 of caller function
-72 saved f6 of caller function
-80 undefined
-96 outgoing args passed from caller to callee
-96+x possible stack alignment ( 8 bytes desirable )
-96+x+y alloca space of caller ( if used )
-96+x+y+z automatics of caller ( if used )
-0 back-chain
+s/390 zSeries
+0 0 back chain ( a 0 here signifies end of back chain )
+4 8 eos ( end of stack, not used on Linux for S390 used in other linkage formats )
+8 16 glue used in other s/390 linkage formats for saved routine descriptors etc.
+12 24 glue used in other s/390 linkage formats for saved routine descriptors etc.
+16 32 scratch area
+20 40 scratch area
+24 48 saved r6 of caller function
+28 56 saved r7 of caller function
+32 64 saved r8 of caller function
+36 72 saved r9 of caller function
+40 80 saved r10 of caller function
+44 88 saved r11 of caller function
+48 96 saved r12 of caller function
+52 104 saved r13 of caller function
+56 112 saved r14 of caller function
+60 120 saved r15 of caller function
+64 128 saved f4 of caller function
+72 132 saved f6 of caller function
+80 undefined
+96 160 outgoing args passed from caller to callee
+96+x 160+x possible stack alignment ( 8 bytes desirable )
+96+x+y 160+x+y alloca space of caller ( if used )
+96+x+y+z 160+x+y+z automatics of caller ( if used )
+0 back-chain
A sample program with comments.
===============================
-Compiling programs for debugging on Linux for S390
-==================================================
-Make sure that the gcc is compiling & linking with the -g flag on
-this generates plain old gnu stabs, don't use
--ggdb, -gxcoff+ or any other silly option these other options more than
-likely don't work ( we haven't tested them ), -gstabs is supposed to add
-extra extensions to the debugging info for debugging c++ we haven't got
-round to testing this yet.
+Compiling programs for debugging on Linux for s/390 & zSeries
+=============================================================
+-gdwarf2 now works & normal -g debugging works much better now
+Thanks to the IBM java compiler developers bug reports.
This is typically done adding/appending the flags -g to the
CFLAGS & LDFLAGS variables Makefile of the program concerned.
parameters will work, however some larger programs which use inline functions
will not compile without optimisation.
+Debugging with optimisation has since much improved after fixing
+some bugs, please make sure you are using gdb-5.0 or later developed
+after Nov'2000.
+
Figuring out gcc compile errors
===============================
If you are getting a lot of syntax errors compiling a program & the problem
as the 390 instructions are easy to decode mentally & you can make a good guess at a lot
of them as all the operands are nibble ( half byte aligned ) & if you have an objdump listing
also it is quite easy to follow, if you don't have an objdump listing keep a copy of
-the ESA Reference Summary & look at between pages 2 & 7 or alternatively the
-ESA principles of operation.
+the s/390 Reference Summary & look at between pages 2 & 7 or alternatively the
+s/390 principles of operation.
e.g. even I can guess that
0001AFF8' LR 180F CC 0
is a ( load register ) lr r0,r15
10=segment translation exception
11=page translation exception
-The full list of these is on page 22 of the current ESA Reference Summary.
+The full list of these is on page 22 of the current s/390 Reference Summary.
e.g.
tr prog 10 will trace segment translation exceptions.
tr prog on its own will trace all program interruption codes.
000151B0' SVC 0A05 -> 0001909A' CC 0
D 20.8 to check the SVC old psw in the prefix area & see was it from userspace
-( for the layout of the prefix area consult P18 of the ESA 390 Reference Summary
+( for the layout of the prefix area consult P18 of the s/390 390 Reference Summary
if you have it available ).
V00000020 070C2000 800151B2
The problem state bit wasn't set & it's also too early in the boot sequence
I am not going to give a course in 390 IO architecture as this would take me quite a
while & I'm no expert. Instead I'll give a 390 IO architecture summary for Dummies if you have
-the ESA principles of operation available read this instead. If nothing else you may find a few
+the s/390 principles of operation available read this instead. If nothing else you may find a few
useful keywords in here & be able to use them on a web search engine like altavista to find
more useful information.
Linux & GDB source.
Various info & man pages.
CMS Help on tracing commands.
+Linux for zSeries and S/390 Elf Application Binary Interface ( Highly Recommended )
+z/Architecture Principles of Operation
IP Fragmentation:
+ip_always_defrag - BOOLEAN
+ 0 - (DEFAULT) Normal setting for a normal router or host.
+ 1 - Reassemble all fragments before processing. Useful for
+ a firewall or transparent proxying hosts.
+
ipfrag_high_thresh - INTEGER
Maximum memory used to reassemble IP fragments. When
ipfrag_high_thresh bytes of memory is allocated for this purpose,
3.6 /proc/sys/dev - Device specific parameters
3.7 /proc/sys/sunrpc - Remote procedure calls
3.8 /proc/sys/net - Networking stuff
-3.9 /proc/sys/net/ipv4 - IPV4 settings=20
+3.9 /proc/sys/net/ipv4 - IPV4 settings
3.10 Appletalk
3.11 IPX
sc_age_cluster_fract
-------------------- * resident set size
- 1024 =20
+ 1024
So if you want kswapd to scan the whole process,
sc_age_cluster_fract needs to have a value of 1024. The minimum
This is automagically enabled when enabling masquerading.
-ipfrag_high_trash and ipfrag_low_trash
+ipfrag_high_tresh and ipfrag_low_tresh
Maximum memory used to reassemble IP fragments. When
ipfrag_high_thresh bytes of memory is allocated for this purpose,
the fragment handler will toss packets until ipfrag_low_thresh is
--- /dev/null
+
+ Debugging on Linux for s/390 & z/Architecture
+ by
+ Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
+ Copyright (C) 2000-2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ Best viewed with fixed width fonts
+
+Overview of Document:
+=====================
+This document is intended to give an good overview of how to debug
+Linux for s/390 & z/Architecture it isn't intended as a complete reference & not a
+tutorial on the fundamentals of C & assembly, it dosen't go into
+390 IO in any detail. It is intended to compliment the documents in the
+reference section below & any other worthwhile references you get.
+
+It is intended like the Enterprise Systems Architecture/390 Reference Summary
+to be printed out & used as a quick cheat sheet self help style reference when
+problems occur.
+
+Contents
+========
+Register Set
+Address Spaces on Intel Linux
+Address Spaces on Linux for s/390 & z/Architecture
+The Linux for s/390 & z/Architecture Kernel Task Structure
+Register Usage & Stackframes on Linux for s/390 & z/Architecture
+A sample program with comments
+Compiling programs for debugging on Linux for s/390 & z/Architecture
+Figuring out gcc compile errors
+Debugging Tools
+objdump
+strace
+Performance Debugging
+Debugging under VM
+s/390 & z/Architecture IO Overview
+Debugging IO on s/390 & z/Architecture under VM
+GDB on s/390 & z/Architecture
+Stack chaining in gdb by hand
+Examining core dumps
+ldd
+Debugging modules
+The proc file system
+Starting points for debugging scripting languages etc.
+SysRq
+References
+Special Thanks
+
+Register Set
+============
+The current architectures have the following registers.
+
+16 General propose registers, 32 bit on s/390 64 bit on z/Architecture, r0-r15 or gpr0-gpr15 used for arithmetic & addressing.
+
+16 Control registers, 32 bit on s/390 64 bit on z/Architecture, ( cr0-cr15 kernel usage only ) used for memory managment,
+interrupt control,debugging control etc.
+
+16 Access registers ( ar0-ar15 ) 32 bit on s/390 & z/Architecture
+not used by normal programs but potentially could
+be used as temporary storage. Their main purpose is their 1 to 1
+association with general purpose registers and are used in
+the kernel for copying data between kernel & user address spaces.
+Access register 0 ( & access register 1 on z/Architecture ( needs 64 bit
+pointer ) ) is currently used by the pthread library as a pointer to
+the current running threads private area.
+
+16 64 bit floating point registers (fp0-fp15 ) IEEE & HFP floating
+point format compliant on G5 upwards & a Floating point control reg (FPC)
+4 64 bit registers (fp0,fp2,fp4 & fp6) HFP only on older machines.
+Note:
+Linux (currently) always uses IEEE & emulates G5 IEEE format on older machines,
+( provided the kernel is configured for this ).
+
+
+The PSW is the most important register on the machine it
+is 64 bit on s/390 & 128 bit on z/Architecture & serves the roles of
+a program counter (pc), condition code register,memory space designator.
+In IBM standard notation I am counting bit 0 as the MSB.
+It has several advantages over a normal program counter
+in that you can change address translation & program counter
+in a single instruction. To change address translation,
+e.g. switching address translation off requires that you
+have a logical=physical mapping for the address you are
+currently running at.
+
+ Bit Value
+s/390 z/Architecture
+0 0 Reserved ( must be 0 ) otherwise specification exception occurs.
+
+1 1 Program Event Recording 1 PER enabled,
+ PER is used to facilititate debugging e.g. single stepping.
+
+2-4 2-4 Reserved ( must be 0 ).
+
+5 5 Dynamic address translation 1=DAT on.
+
+6 6 Input/Output interrupt Mask
+
+7 7 External interrupt Mask used primarily for interprocessor signalling &
+ clock interupts.
+
+8-11 8-11 PSW Key used for complex memory protection mechanism not used under linux
+
+12 12 1 on s/390 0 on z/Architecture
+
+13 13 Machine Check Mask 1=enable machine check interrupts
+
+14 14 Wait State set this to 1 to stop the processor except for interrupts & give
+ time to other LPARS used in CPU idle in the kernel to increase overall
+ usage of processor resources.
+
+15 15 Problem state ( if set to 1 certain instructions are disabled )
+ all linux user programs run with this bit 1
+ ( useful info for debugging under VM ).
+
+16-17 16-17 Address Space Control
+
+ 00 Primary Space Mode when DAT on
+ The linux kernel currently runs in this mode, CR1 is affiliated with
+ this mode & points to the primary segment table origin etc.
+
+ 01 Access register mode this mode is used in functions to
+ copy data between kernel & user space.
+
+ 10 Secondary space mode not used in linux however CR7 the
+ register affiliated with this mode is & this & normally
+ CR13=CR7 to allow us to copy data between kernel & user space.
+ We do this as follows:
+ We set ar2 to 0 to designate its
+ affiliated gpr ( gpr2 )to point to primary=kernel space.
+ We set ar4 to 1 to designate its
+ affiliated gpr ( gpr4 ) to point to secondary=home=user space
+ & then essentially do a memcopy(gpr2,gpr4,size) to
+ copy data between the address spaces, the reason we use home space for the
+ kernel & don't keep secondary space free is that code will not run in
+ secondary space.
+
+ 11 Home Space Mode all user programs run in this mode.
+ it is affiliated with CR13.
+
+18-19 18-19 Condition codes (CC)
+
+20 20 Fixed point overflow mask if 1=FPU exceptions for this event
+ occur ( normally 0 )
+
+21 21 Decimal overflow mask if 1=FPU exceptions for this event occur
+ ( normally 0 )
+
+22 22 Exponent underflow mask if 1=FPU exceptions for this event occur
+ ( normally 0 )
+
+23 23 Significance Mask if 1=FPU exceptions for this event occur
+ ( normally 0 )
+
+24-31 24-30 Reserved Must be 0.
+
+ 31 Extended Addressing Mode
+ 32 Basic Addressing Mode
+ Used to set addressing mode
+ PSW 31 PSW 32
+ 0 0 24 bit
+ 0 1 31 bit
+ 1 1 64 bit
+
+32 1=31 bit addressing mode 0=24 bit addressing mode (for backward
+ compatibility ), linux always runs with this bit set to 1
+
+33-64 Instruction address.
+ 33-63 Reserved must be 0
+ 64-127 Address
+ In 24 bits mode bits 64-103=0 bits 104-127 Address
+ In 31 bits mode bits 64-96=0 bits 97-127 Address
+ Note: unlike 31 bit mode on s/390 bit 96 must be zero
+ when loading the address with LPSWE otherwise a
+ specification exception occurs, LPSW is fully backward
+ compatible.
+
+
+Prefix Page(s)
+--------------
+This per cpu memory area is too intimately tied to the processor not to mention.
+It exists between the real addresses 0-4096 on s/390 & 0-8192 z/Architecture & is exchanged
+with a 1 page on s/390 or 2 pages on z/Architecture in absolute storage by the set
+prefix instruction in linux'es startup.
+This page is mapped to a different prefix for each processor in an SMP configuration
+( assuming the os designer is sane of course :-) ).
+Bytes 0-512 ( 200 hex ) on s/390 & 0-512,4096-4544,4604-5119 currently on z/Architecture
+are used by the processor itself for holding such information as exception indications &
+entry points for exceptions.
+Bytes after 0xc00 hex are used by linux for per processor globals on s/390 & z/Architecture
+( there is a gap on z/Architecure too currently between 0xc00 & 1000 which linux uses ).
+The closest thing to this on traditional architectures is the interrupt
+vector table. This is a good thing & does simplify some of the kernel coding
+however it means that we now cannot catch stray NULL pointers in the
+kernel without hard coded checks.
+
+
+
+Address Spaces on Intel Linux
+=============================
+
+The traditional Intel Linux is approximately mapped as follows forgive
+the ascii art.
+0xFFFFFFFF 4GB Himem *****************
+ * *
+ * Kernel Space *
+ * *
+ ***************** ****************
+User Space Himem (typically 0xC0000000 3GB )* User Stack * * *
+ ***************** * *
+ * Shared Libs * * Next Process *
+ ***************** * to *
+ * * <== * Run * <==
+ * User Program * * *
+ * Data BSS * * *
+ * Text * * *
+ * Sections * * *
+0x00000000 ***************** ****************
+
+Now it is easy to see that on Intel it is quite easy to recognise a kernel address
+as being one greater than user space himem ( in this case 0xC0000000).
+& addresses of less than this are the ones in the current running program on this
+processor ( if an smp box ).
+If using the virtual machine ( VM ) as a debugger it is quite difficult to
+know which user process is running as the address space you are looking at
+could be from any process in the run queue.
+
+The limitation of Intels addressing technique is that the linux
+kernel uses a very simple real address to virtual addressing technique
+of Real Address=Virtual Address-User Space Himem.
+This means that on Intel the kernel linux can typically only address
+Himem=0xFFFFFFFF-0xC0000000=1GB & this is all the RAM these machines
+can typically use.
+They can lower User Himem to 2GB or lower & thus be
+able to use 2GB of RAM however this shrinks the maximum size
+of User Space from 3GB to 2GB they have a no win limit of 4GB unless
+they go to 64 Bit.
+
+
+On 390 our limitations & strengths make us slightly different.
+For backward compatibility we are only allowed use 31 bits (2GB)
+of our 32 bit addresses,however, we use entirely separate address
+spaces for the user & kernel.
+
+This means we can support 2GB of non Extended RAM on s/390, & more
+with the Extended memory managment swap device &
+currently 4TB of physical memory currently on z/Architecture.
+
+
+Address Spaces on Linux for s/390 & z/Architecture
+==================================================
+
+Our addressing scheme is as follows
+
+
+Himem 0x7fffffff 2GB on s/390 ***************** ****************
+currently 0x3ffffffffff (2^42)-1 * User Stack * * *
+on z/Architecture. ***************** * *
+ * Shared Libs * * *
+ ***************** * *
+ * * * Kernel *
+ * User Program * * *
+ * Data BSS * * *
+ * Text * * *
+ * Sections * * *
+0x00000000 ***************** ****************
+
+This also means that we need to look at the PSW problem state bit
+or the addressing mode to decide whether we are looking at
+user or kernel space.
+
+Virtual Addresses on s/390 & z/Architecture
+===========================================
+
+A virtual address on s/390 is made up of 3 parts
+The SX ( segment index, roughly corresponding to the PGD & PMD in linux terminology )
+being bits 1-11.
+The PX ( page index, corresponding to the page table entry (pte) in linux terminology )
+being bits 12-19.
+The remaining bits BX (the byte index are the offset in the page )
+i.e. bits 20 to 31.
+
+On z/Architecture in linux we currently make up an address from 4 parts.
+The region index bits (RX) 0-32 we currently use bits 22-32
+The segment index (SX) being bits 33-43
+The page index (PX) being bits 44-51
+The byte index (BX) being bits 52-63
+
+Notes:
+1) s/390 has no PMD so the PMD is really the PGD also.
+A lot of this stuff is defined in pgtable.h.
+
+2) Also seeing as s/390's page indexes are only 1k in size
+(bits 12-19 x 4 bytes per pte ) we use 1 ( page 4k )
+to make the best use of memory by updating 4 segment indices
+entries each time we mess with a PMD & use offsets
+0,1024,2048 & 3072 in this page as for our segment indexes.
+On z/Architecture our page indexes are now 2k in size
+( bits 12-19 x 8 bytes per pte ) we do a similar trick
+but only mess with 2 segment indices each time we mess with
+a PMD.
+
+3) As z/Architecture supports upto a massive 5-level page table lookup we
+can only use 3 currently on Linux ( as this is all the generic kernel
+currently supports ) however this may change in future
+this allows us to access ( according to my sums )
+4TB of virtual storage per process i.e.
+4096*512(PTES)*1024(PMDS)*2048(PGD) = 4398046511104 bytes,
+enough for another 2 or 3 of years I think :-).
+to do this we use a region-third-table designation type in
+our address space control registers.
+
+
+The Linux for s/390 & z/Architecture Kernel Task Structure
+==========================================================
+Each process/thread under Linux for S390 has its own kernel task_struct
+defined in linux/include/linux/sched.h
+The S390 on initialisation & resuming of a process on a cpu sets
+the __LC_KERNEL_STACK variable in the spare prefix area for this cpu
+( which we use for per processor globals).
+
+The kernel stack pointer is intimately tied with the task stucture for
+each processor as follows.
+
+ s/390
+ ************************
+ * 1 page kernel stack *
+ * ( 4K ) *
+ ************************
+ * 1 page task_struct *
+ * ( 4K ) *
+8K aligned ************************
+
+ z/Architecture
+ ************************
+ * 2 page kernel stack *
+ * ( 8K ) *
+ ************************
+ * 2 page task_struct *
+ * ( 8K ) *
+16K aligned ************************
+
+What this means is that we don't need to dedicate any register or global variable
+to point to the current running process & can retrieve it with the following
+very simple construct for s/390 & one very similar for z/Architecture.
+
+static inline struct task_struct * get_current(void)
+{
+ struct task_struct *current;
+ __asm__("lhi %0,-8192\n\t"
+ "nr %0,15"
+ : "=r" (current) );
+ return current;
+}
+
+i.e. just anding the current kernel stack pointer with the mask -8192.
+Thankfully because Linux dosen't have support for nested IO interrupts
+& our devices have large buffers can survive interrupts being shut for
+short amounts of time we don't need a separate stack for interrupts.
+
+
+
+
+Register Usage & Stackframes on Linux for s/390 & z/Architecture
+=================================================================
+Overview:
+---------
+This is the code that gcc produces at the top & the bottom of
+each function, it usually is fairly consistent & similar from
+function to function & if you know its layout you can probalby
+make some headway in finding the ultimate cause of a problem
+after a crash without a source level debugger.
+
+Note: To follow stackframes requires a knowledge of C or Pascal &
+limited knowledge of one assembly language.
+
+It should be noted that there are some differences between the
+s/390 & z/Architecture stack layouts as the z/Architecture stack layout didn't have
+to maintain compatibility with older linkage formats.
+
+Glossary:
+---------
+alloca:
+This is a built in compiler function for runtime allocation
+of extra space on the callers stack which is obviously freed
+up on function exit ( e.g. the caller may choose to allocate nothing
+of a buffer of 4k if required for temporary purposes ), it generates
+very efficent code ( a few cycles ) when compared to alternatives
+like malloc.
+
+automatics: These are local variables on the stack,
+i.e they aren't in registers & they aren't static.
+
+back-chain:
+This is a pointer to the stack pointer before entering a
+framed functions ( see frameless function ) prologue got by
+deferencing the address of the current stack pointer,
+ i.e. got by accessing the 32 bit value at the stack pointers
+current location.
+
+base-pointer:
+This is a pointer to the back of the literal pool which
+is an area just behind each procedure used to store constants
+in each function.
+
+call-clobbered: The caller probably needs to save these registers if there
+is something of value in them, on the stack or elsewhere before making a
+call to another procedure so that it can restore it later.
+
+epilogue:
+The code generated by the compiler to return to the caller.
+
+frameless-function
+A frameless function in Linux for s390 & z/Architecture is one which doesn't
+need more than the register save area ( 96 bytes on s/390, 160 on z/Architecture )
+given to it by the caller.
+A frameless function never:
+1) Sets up a back chain.
+2) Calls alloca.
+3) Calls other normal functions
+4) Has automatics.
+
+GOT-pointer:
+This is a pointer to the global-offset-table in ELF
+( Executable Linkable Format, Linux'es most common executable format ),
+all globals & shared library objects are found using this pointer.
+
+lazy-binding
+ELF shared libraries are typically only loaded when routines in the shared
+library are actually first called at runtime. This is lazy binding.
+
+procedure-linkage-table
+This is a table found from the GOT which contains pointers to routines
+in other shared libraries which can't be called to by easier means.
+
+prologue:
+The code generated by the compiler to set up the stack frame.
+
+outgoing-args:
+This is extra area allocated on the stack of the calling function if the
+parameters for the callee's cannot all be put in registers, the same
+area can be reused by each function the caller calls.
+
+routine-descriptor:
+A COFF executable format based concept of a procedure reference
+actually being 8 bytes or more as opposed to a simple pointer to the routine.
+This is typically defined as follows
+Routine Descriptor offset 0=Pointer to Function
+Routine Descriptor offset 4=Pointer to Table of Contents
+The table of contents/TOC is roughly equivalent to a GOT pointer.
+& it means that shared libraries etc. can be shared between several
+environments each with their own TOC.
+
+
+static-chain: This is used in nested functions a concept adopted from pascal
+by gcc not used in ansi C or C++ ( although quite useful ), basically it
+is a pointer used to reference local variables of enclosing functions.
+You might come across this stuff once or twice in your lifetime.
+
+e.g.
+The function below should return 11 though gcc may get upset & toss warnings
+about unused variables.
+int FunctionA(int a)
+{
+ int b;
+ FunctionC(int c)
+ {
+ b=c+1;
+ }
+ FunctionC(10);
+ return(b);
+}
+
+
+s/390 & z/Architecture Register usage
+=====================================
+r0 used by syscalls/assembly call-clobbered
+r1 used by syscalls/assembly call-clobbered
+r2 argument 0 / return value 0 call-clobbered
+r3 argument 1 / return value 1 (if long long) call-clobbered
+r4 argument 2 call-clobbered
+r5 argument 3 call-clobbered
+r6 argument 5 saved
+r7 pointer-to arguments 5 to ... saved
+r8 this & that saved
+r9 this & that saved
+r10 static-chain ( if nested function ) saved
+r11 frame-pointer ( if function used alloca ) saved
+r12 got-pointer saved
+r13 base-pointer saved
+r14 return-address saved
+r15 stack-pointer saved
+
+f0 argument 0 / return value ( float/double ) call-clobbered
+f2 argument 1 call-clobbered
+f4 z/Architecture argument 2 saved
+f6 z/Architecture argument 3 saved
+The remaining floating points
+f1,f3,f5 f7-f15 are call-clobbered.
+
+Notes:
+------
+1) The only requirement is that registers which are used
+by the callee are saved, e.g. the compiler is perfectly
+capible of using r11 for purposes other than a frame a
+frame pointer if a frame pointer is not needed.
+2) In functions with variable arguments e.g. printf the calling procedure
+is identical to one without variable arguments & the same number of
+parameters. However, the prologue of this function is somewhat more
+hairy owing to it having to move these parameters to the stack to
+get va_start, va_arg & va_end to work.
+3) Access registers are currently unused by gcc but are used in
+the kernel. Possibilities exist to use them at the moment for
+temporary storage but it isn't recommended.
+4) Only 4 of the floating point registers are used for
+parameter passing as older machines such as G3 only have only 4
+& it keeps the stack frame compatible with other compilers.
+However with IEEE floating point emulation under linux on the
+older machines you are free to use the other 12.
+5) A long long or double parameter cannot be have the
+first 4 bytes in a register & the second four bytes in the
+outgoing args area. It must be purely in the outgoing args
+area if crossing this boundary.
+6) Floating point parameters are mixed with outgoing args
+on the outgoing args area in the order the are passed in as parameters.
+7) Floating point arguments 2 & 3 are saved in the outgoing args area for
+z/Architecture
+
+
+Stack Frame Layout
+------------------
+s/390 z/Architecture
+0 0 back chain ( a 0 here signifies end of back chain )
+4 8 eos ( end of stack, not used on Linux for S390 used in other linkage formats )
+8 16 glue used in other s/390 linkage formats for saved routine descriptors etc.
+12 24 glue used in other s/390 linkage formats for saved routine descriptors etc.
+16 32 scratch area
+20 40 scratch area
+24 48 saved r6 of caller function
+28 56 saved r7 of caller function
+32 64 saved r8 of caller function
+36 72 saved r9 of caller function
+40 80 saved r10 of caller function
+44 88 saved r11 of caller function
+48 96 saved r12 of caller function
+52 104 saved r13 of caller function
+56 112 saved r14 of caller function
+60 120 saved r15 of caller function
+64 128 saved f4 of caller function
+72 132 saved f6 of caller function
+80 undefined
+96 160 outgoing args passed from caller to callee
+96+x 160+x possible stack alignment ( 8 bytes desirable )
+96+x+y 160+x+y alloca space of caller ( if used )
+96+x+y+z 160+x+y+z automatics of caller ( if used )
+0 back-chain
+
+A sample program with comments.
+===============================
+
+Comments on the function test
+-----------------------------
+1) It didn't need to set up a pointer to the constant pool gpr13 as it isn't used
+( :-( ).
+2) This is a frameless function & no stack is bought.
+3) The compiler was clever enough to recognise that it could return the
+value in r2 as well as use it for the passed in parameter ( :-) ).
+4) The basr ( branch relative & save ) trick works as follows the instruction
+has a special case with r0,r0 with some instruction operands is understood as
+the literal value 0, some risc architectures also do this ). So now
+we are branching to the next address & the address new program counter is
+in r13,so now we subtract the size of the function prologue we have executed
++ the size of the literal pool to get to the top of the literal pool
+0040037c int test(int b)
+{ # Function prologue below
+ 40037c: 90 de f0 34 stm %r13,%r14,52(%r15) # Save registers r13 & r14
+ 400380: 0d d0 basr %r13,%r0 # Set up pointer to constant pool using
+ 400382: a7 da ff fa ahi %r13,-6 # basr trick
+ return(5+b);
+ # Huge main program
+ 400386: a7 2a 00 05 ahi %r2,5 # add 5 to r2
+
+ # Function epilogue below
+ 40038a: 98 de f0 34 lm %r13,%r14,52(%r15) # restore registers r13 & 14
+ 40038e: 07 fe br %r14 # return
+}
+
+Comments on the function main
+-----------------------------
+1) The compiler did this function optimally ( 8-) )
+
+Literal pool for main.
+400390: ff ff ff ec .long 0xffffffec
+main(int argc,char *argv[])
+{ # Function prologue below
+ 400394: 90 bf f0 2c stm %r11,%r15,44(%r15) # Save necessary registers
+ 400398: 18 0f lr %r0,%r15 # copy stack pointer to r0
+ 40039a: a7 fa ff a0 ahi %r15,-96 # Make area for callee saving
+ 40039e: 0d d0 basr %r13,%r0 # Set up r13 to point to
+ 4003a0: a7 da ff f0 ahi %r13,-16 # literal pool
+ 4003a4: 50 00 f0 00 st %r0,0(%r15) # Save backchain
+
+ return(test(5)); # Main Program Below
+ 4003a8: 58 e0 d0 00 l %r14,0(%r13) # load relative address of test from
+ # literal pool
+ 4003ac: a7 28 00 05 lhi %r2,5 # Set first parameter to 5
+ 4003b0: 4d ee d0 00 bas %r14,0(%r14,%r13) # jump to test setting r14 as return
+ # address using branch & save instruction.
+
+ # Function Epilogue below
+ 4003b4: 98 bf f0 8c lm %r11,%r15,140(%r15)# Restore necessary registers.
+ 4003b8: 07 fe br %r14 # return to do program exit
+}
+
+
+Compiler updates
+----------------
+
+main(int argc,char *argv[])
+{
+ 4004fc: 90 7f f0 1c stm %r7,%r15,28(%r15)
+ 400500: a7 d5 00 04 bras %r13,400508 <main+0xc>
+ 400504: 00 40 04 f4 .long 0x004004f4
+ # compiler now puts constant pool in code to so it saves an instruction
+ 400508: 18 0f lr %r0,%r15
+ 40050a: a7 fa ff a0 ahi %r15,-96
+ 40050e: 50 00 f0 00 st %r0,0(%r15)
+ return(test(5));
+ 400512: 58 10 d0 00 l %r1,0(%r13)
+ 400516: a7 28 00 05 lhi %r2,5
+ 40051a: 0d e1 basr %r14,%r1
+ # compiler adds 1 extra instruction to epilogue this is done to
+ # avoid processor pipeline stalls owing to data dependencies on g5 &
+ # above as register 14 in the old code was needed directly after being loaded
+ # by the lm %r11,%r15,140(%r15) for the br %14.
+ 40051c: 58 40 f0 98 l %r4,152(%r15)
+ 400520: 98 7f f0 7c lm %r7,%r15,124(%r15)
+ 400524: 07 f4 br %r4
+}
+
+
+Hartmut ( our compiler developer ) also has been threatening to take out the
+stack backchain in optimised code as this also causes pipeline stalls, you
+have been warned.
+
+64 bit z/Architecture code disassembly
+--------------------------------------
+
+If you understand the stuff above you'll understand the stuff
+below too so I'll avoid repeating myself & just say that
+some of the instructions have g's on the end of them to indicate
+they are 64 bit & the stack offsets are a bigger,
+the only other difference you'll find between 32 & 64 bit is that
+we now use f4 & f6 for floating point arguments on 64 bit.
+00000000800005b0 <test>:
+int test(int b)
+{
+ return(5+b);
+ 800005b0: a7 2a 00 05 ahi %r2,5
+ 800005b4: b9 14 00 22 lgfr %r2,%r2 # downcast to integer
+ 800005b8: 07 fe br %r14
+ 800005ba: 07 07 bcr 0,%r7
+
+
+}
+
+00000000800005bc <main>:
+main(int argc,char *argv[])
+{
+ 800005bc: eb bf f0 58 00 24 stmg %r11,%r15,88(%r15)
+ 800005c2: b9 04 00 1f lgr %r1,%r15
+ 800005c6: a7 fb ff 60 aghi %r15,-160
+ 800005ca: e3 10 f0 00 00 24 stg %r1,0(%r15)
+ return(test(5));
+ 800005d0: a7 29 00 05 lghi %r2,5
+ # brasl allows jumps > 64k & is overkill here bras would do fune
+ 800005d4: c0 e5 ff ff ff ee brasl %r14,800005b0 <test>
+ 800005da: e3 40 f1 10 00 04 lg %r4,272(%r15)
+ 800005e0: eb bf f0 f8 00 04 lmg %r11,%r15,248(%r15)
+ 800005e6: 07 f4 br %r4
+}
+
+
+
+Compiling programs for debugging on Linux for s/390 & z/Architecture
+====================================================================
+-gdwarf-2 now works it should be considered the default debugging
+format for s/390 & z/Architecture as it is more reliable for debugging
+shared libraries, normal -g debugging works much better now
+Thanks to the IBM java compiler developers bug reports.
+
+This is typically done adding/appending the flags -g or -gdwarf-2 to the
+CFLAGS & LDFLAGS variables Makefile of the program concerned.
+
+If using gdb & you would like accurate displays of registers &
+ stack traces compile without optimisation i.e make sure
+that there is no -O2 or similar on the CFLAGS line of the Makefile &
+the emitted gcc commands, obviously this will produce worse code
+( not advisable for shipment ) but it is an aid to the debugging process.
+
+This aids debugging because the compiler will copy parameters passed in
+in registers onto the stack so backtracing & looking at passed in
+parameters will work, however some larger programs which use inline functions
+will not compile without optimisation.
+
+Debugging with optimisation has since much improved after fixing
+some bugs, please make sure you are using gdb-5.0 or later developed
+after Nov'2000.
+
+Figuring out gcc compile errors
+===============================
+If you are getting a lot of syntax errors compiling a program & the problem
+isn't blatantly obvious from the source.
+It often helps to just preprocess the file, this is done with the -E
+option in gcc.
+What this does is that it runs through the very first phase of compilation
+( compilation in gcc is done in several stages & gcc calls many programs to
+achieve its end result ) with the -E option gcc just calls the gcc preprocessor (cpp).
+The c preprocessor does the following, it joins all the files #included together
+recursively ( #include files can #include other files ) & also the c file you wish to compile.
+It puts a fully qualified path of the #included files in a comment & it
+does macro expansion.
+This is useful for debugging because
+1) You can double check whether the files you expect to be included are the ones
+that are being included ( e.g. double check that you aren't going to the i386 asm directory ).
+2) Check that macro definitions aren't clashing with typedefs,
+3) Check that definitons aren't being used before they are being included.
+4) Helps put the line emitting the error under the microscope if it contains macros.
+
+For convenience the Linux kernel's makefile will do preprocessing automatically for you
+by suffixing the file you want built with .i ( instead of .o )
+
+e.g.
+from the linux directory type
+make arch/s390/kernel/signal.i
+this will build
+
+s390-gcc -D__KERNEL__ -I/home1/barrow/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
+-fno-strict-aliasing -D__SMP__ -pipe -fno-strength-reduce -E arch/s390/kernel/signal.c
+> arch/s390/kernel/signal.i
+
+Now look at signal.i you should see something like.
+
+
+# 1 "/home1/barrow/linux/include/asm/types.h" 1
+typedef unsigned short umode_t;
+typedef __signed__ char __s8;
+typedef unsigned char __u8;
+typedef __signed__ short __s16;
+typedef unsigned short __u16;
+
+If instead you are getting errors further down e.g.
+unknown instruction:2515 "move.l" or better still unknown instruction:2515
+"Fixme not implemented yet, call Martin" you are probably are attempting to compile some code
+meant for another architecture or code that is simply not implemented, with a fixme statement
+stuck into the inline assembly code so that the author of the file now knows he has work to do.
+To look at the assembly emitted by gcc just before it is about to call gas ( the gnu assembler )
+use the -S option.
+Again for your convenience the Linux kernel's Makefile will hold your hand &
+do all this donkey work for you also by building the file with the .s suffix.
+e.g.
+from the Linux directory type
+make arch/s390/kernel/signal.s
+
+s390-gcc -D__KERNEL__ -I/home1/barrow/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
+-fno-strict-aliasing -D__SMP__ -pipe -fno-strength-reduce -S arch/s390/kernel/signal.c
+-o arch/s390/kernel/signal.s
+
+
+This will output something like, ( please note the constant pool & the useful comments
+in the prologue to give you a hand at interpreting it ).
+
+.LC54:
+ .string "misaligned (__u16 *) in __xchg\n"
+.LC57:
+ .string "misaligned (__u32 *) in __xchg\n"
+.L$PG1: # Pool sys_sigsuspend
+.LC192:
+ .long -262401
+.LC193:
+ .long -1
+.LC194:
+ .long schedule-.L$PG1
+.LC195:
+ .long do_signal-.L$PG1
+ .align 4
+.globl sys_sigsuspend
+ .type sys_sigsuspend,@function
+sys_sigsuspend:
+# leaf function 0
+# automatics 16
+# outgoing args 0
+# need frame pointer 0
+# call alloca 0
+# has varargs 0
+# incoming args (stack) 0
+# function length 168
+ STM 8,15,32(15)
+ LR 0,15
+ AHI 15,-112
+ BASR 13,0
+.L$CO1: AHI 13,.L$PG1-.L$CO1
+ ST 0,0(15)
+ LR 8,2
+ N 5,.LC192-.L$PG1(13)
+
+Adding -g to the above output makes the output even more useful
+e.g. typing
+make CC:="s390-gcc -g" kernel/sched.s
+
+which compiles.
+s390-gcc -g -D__KERNEL__ -I/home/barrow/linux-2.3/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -fno-strength-reduce -S kernel/sched.c -o kernel/sched.s
+
+also outputs stabs ( debugger ) info, from this info you can find out the
+offsets & sizes of various elements in structures.
+e.g. the stab for the structure
+struct rlimit {
+ unsigned long rlim_cur;
+ unsigned long rlim_max;
+};
+is
+.stabs "rlimit:T(151,2)=s8rlim_cur:(0,5),0,32;rlim_max:(0,5),32,32;;",128,0,0,0
+from this stab you can see that
+rlimit_cur starts at bit offset 0 & is 32 bits in size
+rlimit_max starts at bit offset 32 & is 32 bits in size.
+
+
+Debugging Tools:
+================
+
+objdump
+=======
+This is a tool with many options the most useful being ( if compiled with -g).
+objdump --source <victim program or object file> > <victims debug listing >
+
+
+The whole kernel can be compiled like this ( Doing this will make a 17MB kernel
+& a 200 MB listing ) however you have to strip it before building the image
+using the strip command to make it a more reasonable size to boot it.
+
+A source/assembly mixed dump of the kernel can be done with the line
+objdump --source vmlinux > vmlinux.lst
+Also if the file isn't compiled -g this will output as much debugging information
+as it can ( e.g. function names ), however, this is very slow as it spends lots
+of time searching for debugging info, the following self explanitory line should be used
+instead if the code isn't compiled -g.
+objdump --disassemble-all --syms vmlinux > vmlinux.lst
+as it is much faster
+
+As hard drive space is valuble most of us use the following approach.
+1) Look at the emitted psw on the console to find the crash address in the kernel.
+2) Look at the file System.map ( in the linux directory ) produced when building
+the kernel to find the closest address less than the current PSW to find the
+offending function.
+3) use grep or similar to search the source tree looking for the source file
+ with this function if you don't know where it is.
+4) rebuild this object file with -g on, as an example suppose the file was
+( /arch/s390/kernel/signal.o )
+5) Assuming the file with the erroneous function is signal.c Move to the base of the
+Linux source tree.
+6) rm /arch/s390/kernel/signal.o
+7) make /arch/s390/kernel/signal.o
+8) watch the gcc command line emitted
+9) type it in again or alernatively cut & paste it on the console adding the -g option.
+10) objdump --source arch/s390/kernel/signal.o > signal.lst
+This will output the source & the assembly intermixed, as the snippet below shows
+This will unfortunately output addresses which aren't the same
+as the kernel ones you should be able to get around the mental arithmetic
+by playing with the --adjust-vma parameter to objdump.
+
+
+
+
+extern inline void spin_lock(spinlock_t *lp)
+{
+ a0: 18 34 lr %r3,%r4
+ a2: a7 3a 03 bc ahi %r3,956
+ __asm__ __volatile(" lhi 1,-1\n"
+ a6: a7 18 ff ff lhi %r1,-1
+ aa: 1f 00 slr %r0,%r0
+ ac: ba 01 30 00 cs %r0,%r1,0(%r3)
+ b0: a7 44 ff fd jm aa <sys_sigsuspend+0x2e>
+ saveset = current->blocked;
+ b4: d2 07 f0 68 mvc 104(8,%r15),972(%r4)
+ b8: 43 cc
+ return (set->sig[0] & mask) != 0;
+}
+
+6) If debugging under VM go down to that section in the document for more info.
+
+
+I now have a tool which takes the pain out of --adjust-vma
+& you are able to do something like
+make /arch/s390/kernel/traps.lst
+& it automatically generates the correctly relocated entries for
+the text segment in traps.lst.
+This tool is now standard in linux distro's in scripts/makelst
+
+strace:
+-------
+Q. What is it ?
+A. It is a tool for intercepting calls to the kernel & logging them
+to a file & on the screen.
+
+Q. What use is it ?
+A. You can used it to find out what files a particular program opens.
+
+
+
+Example 1
+---------
+If you wanted to know does ping work but didn't have the source
+strace ping -c 1 127.0.0.1
+& then look at the man pages for each of the syscalls below,
+( In fact this is sometimes easier than looking at some spagetti
+source which conditionally compiles for several architectures )
+Not everything that it throws out needs to make sense immeadiately
+
+Just looking quickly you can see that it is making up a RAW socket
+for the ICMP protocol.
+Doing an alarm(10) for a 10 second timeout
+& doing a gettimeofday call before & after each read to see
+how long the replies took, & writing some text to stdout so the user
+has an idea what is going on.
+
+socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = 3
+getuid() = 0
+setuid(0) = 0
+stat("/usr/share/locale/C/libc.cat", 0xbffff134) = -1 ENOENT (No such file or directory)
+stat("/usr/share/locale/libc/C", 0xbffff134) = -1 ENOENT (No such file or directory)
+stat("/usr/local/share/locale/C/libc.cat", 0xbffff134) = -1 ENOENT (No such file or directory)
+getpid() = 353
+setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
+setsockopt(3, SOL_SOCKET, SO_RCVBUF, [49152], 4) = 0
+fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(3, 1), ...}) = 0
+mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40008000
+ioctl(1, TCGETS, {B9600 opost isig icanon echo ...}) = 0
+write(1, "PING 127.0.0.1 (127.0.0.1): 56 d"..., 42PING 127.0.0.1 (127.0.0.1): 56 data bytes
+) = 42
+sigaction(SIGINT, {0x8049ba0, [], SA_RESTART}, {SIG_DFL}) = 0
+sigaction(SIGALRM, {0x8049600, [], SA_RESTART}, {SIG_DFL}) = 0
+gettimeofday({948904719, 138951}, NULL) = 0
+sendto(3, "\10\0D\201a\1\0\0\17#\2178\307\36"..., 64, 0, {sin_family=AF_INET,
+sin_port=htons(0), sin_addr=inet_addr("127.0.0.1")}, 16) = 64
+sigaction(SIGALRM, {0x8049600, [], SA_RESTART}, {0x8049600, [], SA_RESTART}) = 0
+sigaction(SIGALRM, {0x8049ba0, [], SA_RESTART}, {0x8049600, [], SA_RESTART}) = 0
+alarm(10) = 0
+recvfrom(3, "E\0\0T\0005\0\0@\1|r\177\0\0\1\177"..., 192, 0,
+{sin_family=AF_INET, sin_port=htons(50882), sin_addr=inet_addr("127.0.0.1")}, [16]) = 84
+gettimeofday({948904719, 160224}, NULL) = 0
+recvfrom(3, "E\0\0T\0006\0\0\377\1\275p\177\0"..., 192, 0,
+{sin_family=AF_INET, sin_port=htons(50882), sin_addr=inet_addr("127.0.0.1")}, [16]) = 84
+gettimeofday({948904719, 166952}, NULL) = 0
+write(1, "64 bytes from 127.0.0.1: icmp_se"...,
+5764 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=28.0 ms
+
+Example 2
+---------
+strace passwd 2>&1 | grep open
+produces the following output
+open("/etc/ld.so.cache", O_RDONLY) = 3
+open("/opt/kde/lib/libc.so.5", O_RDONLY) = -1 ENOENT (No such file or directory)
+open("/lib/libc.so.5", O_RDONLY) = 3
+open("/dev", O_RDONLY) = 3
+open("/var/run/utmp", O_RDONLY) = 3
+open("/etc/passwd", O_RDONLY) = 3
+open("/etc/shadow", O_RDONLY) = 3
+open("/etc/login.defs", O_RDONLY) = 4
+open("/dev/tty", O_RDONLY) = 4
+
+The 2>&1 is done to redirect stderr to stdout & grep is then filtering this input
+through the pipe for each line containing the string open.
+
+
+Example 3
+---------
+Getting sophistocated
+telnetd crashes on & I don't know why
+Steps
+-----
+1) Replace the following line in /etc/inetd.conf
+telnet stream tcp nowait root /usr/sbin/in.telnetd -h
+with
+telnet stream tcp nowait root /blah
+
+2) Create the file /blah with the following contents to start tracing telnetd
+#!/bin/bash
+/usr/bin/strace -o/t1 -f /usr/sbin/in.telnetd -h
+3) chmod 700 /blah to make it executable only to root
+4)
+killall -HUP inetd
+or ps aux | grep inetd
+get inetd's process id
+& kill -HUP inetd to restart it.
+
+Important options
+-----------------
+-o is used to tell strace to output to a file in our case t1 in the root directory
+-f is to follow children i.e.
+e.g in our case above telnetd will start the login process & subsequently a shell like bash.
+You will be able to tell which is which from the process ID's listed on the left hand side
+of the strace output.
+-p<pid> will tell strace to attach to a running process, yup this can be done provided
+ it isn't being traced or debugged already & you have enough privileges,
+the reason 2 processes cannot trace or debug the same program is that strace
+becomes the parent process of the one being debugged & processes ( unlike people )
+can have only one parent.
+
+
+However the file /t1 will get big quite quickly
+to test it telnet 127.0.0.1
+
+now look at what files in.telnetd execve'd
+413 execve("/usr/sbin/in.telnetd", ["/usr/sbin/in.telnetd", "-h"], [/* 17 vars */]) = 0
+414 execve("/bin/login", ["/bin/login", "-h", "localhost", "-p"], [/* 2 vars */]) = 0
+
+Whey it worked!.
+
+
+Other hints:
+------------
+If the program is not very interactive ( i.e. not much keyboard input )
+& is crashing in one architecture but not in another you can do
+an strace of both programs under as identical a scenario as you can
+on both architectures outputting to a file then.
+do a diff of the two traces using the diff program
+i.e.
+diff output1 output2
+& maybe you'll be able to see where the call paths differed, this
+is possibly near the cause of the crash.
+
+More info
+---------
+Look at man pages for strace & the various syscalls
+e.g. man strace, man alarm, man socket.
+
+
+Performance Debugging
+=====================
+gcc is capible of compiling in profiling code just add the -p option
+to the CFLAGS, this obviously affects program size & performance.
+This can be used by the gprof gnu profiling tool or the
+gcov the gnu code coverage tool ( code coverage is a means of testing
+code quality by checking if all the code in an executable in exercised by
+a tester ).
+
+
+Using top to find out where processes are sleeping in the kernel
+----------------------------------------------------------------
+To do this copy the System.map from the root directory where
+the linux kernel was built to the /boot directory on your
+linux machine.
+Start top
+Now type fU<return>
+You should see a new field called WCHAN which
+tells you where each process is sleeping here is a typical output.
+
+ 6:59pm up 41 min, 1 user, load average: 0.00, 0.00, 0.00
+28 processes: 27 sleeping, 1 running, 0 zombie, 0 stopped
+CPU states: 0.0% user, 0.1% system, 0.0% nice, 99.8% idle
+Mem: 254900K av, 45976K used, 208924K free, 0K shrd, 28636K buff
+Swap: 0K av, 0K used, 0K free 8620K cached
+
+ PID USER PRI NI SIZE RSS SHARE WCHAN STAT LIB %CPU %MEM TIME COMMAND
+ 750 root 12 0 848 848 700 do_select S 0 0.1 0.3 0:00 in.telnetd
+ 767 root 16 0 1140 1140 964 R 0 0.1 0.4 0:00 top
+ 1 root 8 0 212 212 180 do_select S 0 0.0 0.0 0:00 init
+ 2 root 9 0 0 0 0 down_inte SW 0 0.0 0.0 0:00 kmcheck
+
+The time command
+----------------
+Another related command is the time command which gives you an indication
+of where a process is spending the majority of its time.
+e.g.
+time ping -c 5 nc
+outputs
+real 0m4.054s
+user 0m0.010s
+sys 0m0.010s
+
+Debugging under VM
+==================
+
+Notes
+-----
+Addresses & values in the VM debugger are always hex never decimal
+Address ranges are of the format <HexValue1>-<HexValue2> or <HexValue1>.<HexValue2>
+e.g. The address range 0x2000 to 0x3000 can be described described as
+2000-3000 or 2000.1000
+
+The VM Debugger is case insensitive.
+
+VM's strengths are usually other debuggers weaknesses you can get at any resource
+no matter how sensitive e.g. memory managment resources,change address translation
+in the PSW. For kernel hacking you will reap dividends if you get good at it.
+
+The VM Debugger displays operators but not operands, probably because some
+of it was written when memory was expensive & the programmer was probably proud that
+it fitted into 2k of memory & the programmers & didn't want to shock hardcore VM'ers by
+changing the interface :-), also the debugger displays useful information on the same line &
+the author of the code probably felt that it was a good idea not to go over
+the 80 columns on the screen.
+
+As some of you are probably in a panic now this isn't as unintuitive as it may seem
+as the 390 instructions are easy to decode mentally & you can make a good guess at a lot
+of them as all the operands are nibble ( half byte aligned ) & if you have an objdump listing
+also it is quite easy to follow, if you don't have an objdump listing keep a copy of
+the s/390 Reference Summary & look at between pages 2 & 7 or alternatively the
+s/390 principles of operation.
+e.g. even I can guess that
+0001AFF8' LR 180F CC 0
+is a ( load register ) lr r0,r15
+
+Also it is very easy to tell the length of a 390 instruction from the 2 most significant
+bits in the instruction ( not that this info is really useful except if you are trying to
+make sense of a hexdump of code ).
+Here is a table
+Bits Instruction Length
+------------------------------------------
+00 2 Bytes
+01 4 Bytes
+10 4 Bytes
+11 6 Bytes
+
+
+
+
+The debugger also displays other useful info on the same line such as the
+addresses being operated on destination addresses of branches & condition codes.
+e.g.
+00019736' AHI A7DAFF0E CC 1
+000198BA' BRC A7840004 -> 000198C2' CC 0
+000198CE' STM 900EF068 >> 0FA95E78 CC 2
+
+
+
+Useful VM debugger commands
+---------------------------
+
+I suppose I'd better mention this before I start
+to list the current active traces do
+Q TR
+there can be a maximum of 255 of these per set
+( more about trace sets later ).
+To stop traces issue a
+TR END.
+To delete a particular breakpoint issue
+TR DEL <breakpoint number>
+
+The PA1 key drops to CP mode so you can issue debugger commands,
+Doing alt c (on my 3270 console at least ) clears the screen.
+hitting b <enter> comes back to the running operating system
+from cp mode ( in our case linux ).
+It is typically useful to add shortcuts to your profile.exec file
+if you have one ( this is roughly equivalent to autoexec.bat in DOS ).
+file here are a few from mine.
+/* this gives me command history on issuing f12 */
+set pf12 retrieve
+/* this continues */
+set pf8 imm b
+/* goes to trace set a */
+set pf1 imm tr goto a
+/* goes to trace set b */
+set pf2 imm tr goto b
+/* goes to trace set c */
+set pf3 imm tr goto c
+
+
+
+Instruction Tracing
+-------------------
+Setting a simple breakpoint
+TR I PSWA <address>
+To debug a particular function try
+TR I R <function address range>
+TR I on its own will single step.
+TR I DATA <MNEMONIC> <OPTIONAL RANGE> will trace for particular mnemonics
+e.g.
+TR I DATA 4D R 0197BC.4000
+will trace for BAS'es ( opcode 4D ) in the range 0197BC.4000
+if you were inclined you could add traces for all branch instructions &
+suffix them with the run prefix so you would have a backtrace on screen
+when a program crashes.
+TR BR <INTO OR FROM> will trace branches into or out of an address.
+e.g.
+TR BR INTO 0 is often quite useful if a program is getting awkward & deciding
+to branch to 0 & crashing as this will stop at the address before in jumps to 0.
+TR I R <address range> RUN cmd d g
+single steps a range of addresses but stays running &
+displays the gprs on each step.
+
+
+
+Displaying & modifying Registers
+--------------------------------
+D G will display all the gprs
+Adding a extra G to all the commands is neccessary to access the full 64 bit
+content in VM on z/Architecture obviously this isn't required for access registers
+as these are still 32 bit.
+e.g. DGG instead of DG
+D X will display all the control registers
+D AR will display all the access registers
+D AR4-7 will display access registers 4 to 7
+CPU ALL D G will display the GRPS of all CPUS in the configuration
+D PSW will display the current PSW
+st PSW 2000 will put the value 2000 into the PSW &
+cause crash your machine.
+D PREFIX displays the prefix offset
+
+
+Displaying Memory
+-----------------
+To display memory mapped using the current PSW's mapping try
+D <range>
+To make VM display a message each time it hits a particular address & continue try
+D I<range> will disassemble/display a range of instructions.
+ST addr 32 bit word will store a 32 bit aligned address
+D T<range> will display the EBCDIC in an address ( if you are that way inclined )
+D R<range> will display real addresses ( without DAT ) but with prefixing.
+There are other complex options to display if you need to get at say home space
+but are in primary space the easiest thing to do is to temporarily
+modify the PSW to the other addressing mode, display the stuff & then
+restore it.
+
+
+
+Hints
+-----
+If you want to issue a debugger command without halting your virtual machine with the
+PA1 key try prefixing the command with #CP e.g.
+#cp tr i pswa 2000
+also suffixing most debugger commands with RUN will cause them not
+to stop just display the mnemonic at the current instruction on the console.
+If you have several breakpoints you want to put into your program &
+you get fed up of cross referencing with System.map
+you can do the following trick for several symbols.
+grep do_signal System.map
+which emits the following among other things
+0001f4e0 T do_signal
+now you can do
+
+TR I PSWA 0001f4e0 cmd msg * do_signal
+This sends a message to your own console each time do_signal is entered.
+( As an aside I wrote a perl script once which automatically generated a REXX
+script with breakpoints on every kernel procedure, this isn't a good idea
+because there are thousands of these routines & VM can only set 255 breakpoints
+at a time so you nearly had to spend as long pruning the file down as you would
+entering the msg's by hand ),however, the trick might be useful for a single object file.
+On linux'es 3270 emulator x3270 there is a very useful option under the file ment
+Save Screens In File this is very good of keeping a copy of traces.
+
+From CMS help <command name> will give you online help on a particular command.
+e.g.
+HELP DISPLAY
+
+Also CP has a file called profile.exec which automatically gets called
+on startup of CMS ( like autoexec.bat ), keeping on a DOS analogy session
+CP has a feature similar to doskey, it may be useful for you to
+use profile.exec to define some keystrokes.
+e.g.
+SET PF9 IMM B
+This does a single step in VM on pressing F8.
+SET PF10 ^
+This sets up the ^ key.
+which can be used for ^c (ctrl-c),^z (ctrl-z) which can't be typed directly into some 3270 consoles.
+SET PF11 ^-
+This types the starting keystrokes for a sysrq see SysRq below.
+SET PF12 RETRIEVE
+This retrieves command history on pressing F12.
+
+
+Sometimes in VM the display is set up to scroll automatically this
+can be very annoying if there are messages you wish to look at
+to stop this do
+TERM MORE 255 255
+This will nearly stop automatic screen updates, however it will
+cause a denial of service if lots of messages go to the 3270 console,
+so it would be foolish to use this as the default on a production machine.
+
+
+Tracing particular processes
+----------------------------
+The kernels text segment is intentionally at an address in memory that it will
+very seldom collide with text segments of user programs ( thanks Martin ),
+this simplifies debugging the kernel.
+However it is quite common for user processes to have addresses which collide
+this can make debugging a particular process under VM painful under normal
+circumstances as the process may change when doing a
+TR I R <address range>.
+Thankfully after reading VM's online help I figured out how to debug
+I particular process.
+
+Your first problem is to find the STD ( segment table designation )
+of the program you wish to debug.
+There are several ways you can do this here are a few
+1) objdump --syms <program to be debugged> | grep main
+To get the address of main in the program.
+tr i pswa <address of main>
+Start the program, if VM drops to CP on what looks like the entry
+point of the main function this is most likely the process you wish to debug.
+Now do a D X13 or D XG13 on z/Architecture.
+On 31 bit the STD is bits 1-19 ( the STO segment table origin )
+& 25-31 ( the STL segment table length ) of CR13.
+now type
+TR I R STD <CR13's value> 0.7fffffff
+e.g.
+TR I R STD 8F32E1FF 0.7fffffff
+Another very useful variation is
+TR STORE INTO STD <CR13's value> <address range>
+for finding out when a particular variable changes.
+
+An alternative way of finding the STD of a currently running process
+is to do the following, ( this method is more complex but
+could be quite convient if you aren't updating the kernel much &
+so your kernel structures will stay constant for a reasonable period of
+time ).
+
+grep task /proc/<pid>/status
+from this you should see something like
+task: 0f160000 ksp: 0f161de8 pt_regs: 0f161f68
+This now gives you a pointer to the task structure.
+Now make CC:="s390-gcc -g" kernel/sched.s
+To get the task_struct stabinfo.
+( task_struct is defined in include/linux/sched.h ).
+Now we want to look at
+task->active_mm->pgd
+on my machine the active_mm in the task structure stab is
+active_mm:(4,12),672,32
+its offset is 672/8=84=0x54
+the pgd member in the mm_struct stab is
+pgd:(4,6)=*(29,5),96,32
+so its offset is 96/8=12=0xc
+
+so we'll
+hexdump -s 0xf160054 /dev/mem | more
+i.e. task_struct+active_mm offset
+to look at the active_mm member
+f160054 0fee cc60 0019 e334 0000 0000 0000 0011
+hexdump -s 0x0feecc6c /dev/mem | more
+i.e. active_mm+pgd offset
+feecc6c 0f2c 0000 0000 0001 0000 0001 0000 0010
+we get something like
+now do
+TR I R STD <pgd|0x7f> 0.7fffffff
+i.e. the 0x7f is added because the pgd only
+gives the page table origin & we need to set the low bits
+to the maximum possible segment table length.
+TR I R STD 0f2c007f 0.7fffffff
+on z/Architecture you'll probably need to do
+TR I R STD <pgd|0x7> 0.ffffffffffffffff
+to set the TableType to 0x1 & the Table length to 3.
+
+
+
+Tracing Program Exceptions
+--------------------------
+If you get a crash which says something like
+illegal operation or specification exception followed by a register dump
+You can restart linux & trace these using the tr prog <range or value> trace option.
+
+
+
+The most common ones you will normally be tracing for is
+1=operation exception
+2=privileged operation exception
+4=protection exception
+5=addressing exception
+6=specification exception
+10=segment translation exception
+11=page translation exception
+
+The full list of these is on page 22 of the current s/390 Reference Summary.
+e.g.
+tr prog 10 will trace segment translation exceptions.
+tr prog on its own will trace all program interruption codes.
+
+Trace Sets
+----------
+On starting VM you are initially in the INITIAL trace set.
+You can do a Q TR to verify this.
+If you have a complex tracing situation where you wish to wait for instance
+till a driver is open before you start tracing IO, but know in your
+heart that you are going to have to make several runs through the code till you
+have a clue whats going on.
+
+What you can do is
+TR I PSWA <Driver open address>
+hit b to continue till breakpoint
+reach the breakpoint
+now do your
+TR GOTO B
+TR IO 7c08-7c09 inst int run
+or whatever the IO channels you wish to trace are & hit b
+
+To got back to the initial trace set do
+TR GOTO INITIAL
+& the TR I PSWA <Driver open address> will be the only active breakpoint again.
+
+
+Tracing linux syscalls under VM
+-------------------------------
+Syscalls are implemented on Linux for S390 by the Supervisor call instruction (SVC) there 256
+possibilities of these as the instruction is made up of a 0xA opcode & the second byte being
+the syscall number. They are traced using the simple command.
+TR SVC <Optional value or range>
+the syscalls are defined in linux/include/asm-s390/unistd.h
+e.g. to trace all file opens just do
+TR SVC 5 ( as this is the syscall number of open )
+
+
+SMP Specific commands
+---------------------
+To find out how many cpus you have
+Q CPUS displays all the CPU's available to your virtual machine
+To find the cpu that the current cpu VM debugger commands are being directed at do
+Q CPU to change the current cpu cpu VM debugger commands are being directed at do
+CPU <desired cpu no>
+
+On a SMP guest issue a command to all CPUs try prefixing the command with cpu all.
+To issue a command to a particular cpu try cpu <cpu number> e.g.
+CPU 01 TR I R 2000.3000
+If you are running on a guest with several cpus & you have a IO related problem
+& cannot follow the flow of code but you know it isnt smp related.
+from the bash prompt issue
+shutdown -h now or halt.
+do a Q CPUS to find out how many cpus you have
+detach each one of them from cp except cpu 0
+by issueing a
+DETACH CPU 01-(number of cpus in configuration)
+& boot linux again.
+TR SIGP will trace inter processor signal processor instructions.
+DEFINE CPU 01-(number in configuration)
+will get your guests cpus back.
+
+
+Help for displaying ascii textstrings
+-------------------------------------
+On the very latest VM Nucleus'es VM can now display ascii
+( thanks Neale for the hint ) by doing
+D TX<lowaddr>.<len>
+e.g.
+D TX0.100
+
+Alternatively
+=============
+Under older VM debuggers ( I love EBDIC too ) you can use this little program I wrote which
+will convert a command line of hex digits to ascii text which can be compiled under linux &
+you can copy the hex digits from your x3270 terminal to your xterm if you are debugging
+from a linuxbox.
+
+This is quite useful when looking at a parameter passed in as a text string
+under VM ( unless you are good at decoding ASCII in your head ).
+
+e.g. consider tracing an open syscall
+TR SVC 5
+We have stopped at a breakpoint
+000151B0' SVC 0A05 -> 0001909A' CC 0
+
+D 20.8 to check the SVC old psw in the prefix area & see was it from userspace
+( for the layout of the prefix area consult P18 of the s/390 390 Reference Summary
+if you have it available ).
+V00000020 070C2000 800151B2
+The problem state bit wasn't set & it's also too early in the boot sequence
+for it to be a userspace SVC if it was we would have to temporarily switch the
+psw to user space addressing so we could get at the first parameter of the open in
+gpr2.
+Next do a
+D G2
+GPR 2 = 00014CB4
+Now display what gpr2 is pointing to
+D 00014CB4.20
+V00014CB4 2F646576 2F636F6E 736F6C65 00001BF5
+V00014CC4 FC00014C B4001001 E0001000 B8070707
+Now copy the text till the first 00 hex ( which is the end of the string
+to an xterm & do hex2ascii on it.
+hex2ascii 2F646576 2F636F6E 736F6C65 00
+outputs
+Decoded Hex:=/ d e v / c o n s o l e 0x00
+We were opening the console device,
+
+You can compile the code below yourself for practice :-),
+/*
+ * hex2ascii.c
+ * a useful little tool for converting a hexadecimal command line to ascii
+ *
+ * Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
+ * (C) 2000 IBM Deutschland Entwicklung GmbH, IBM Corporation.
+ */
+#include <stdio.h>
+
+int main(int argc,char *argv[])
+{
+ int cnt1,cnt2,len,toggle=0;
+ int startcnt=1;
+ unsigned char c,hex;
+
+ if(argc>1&&(strcmp(argv[1],"-a")==0))
+ startcnt=2;
+ printf("Decoded Hex:=");
+ for(cnt1=startcnt;cnt1<argc;cnt1++)
+ {
+ len=strlen(argv[cnt1]);
+ for(cnt2=0;cnt2<len;cnt2++)
+ {
+ c=argv[cnt1][cnt2];
+ if(c>='0'&&c<='9')
+ c=c-'0';
+ if(c>='A'&&c<='F')
+ c=c-'A'+10;
+ if(c>='a'&&c<='F')
+ c=c-'a'+10;
+ switch(toggle)
+ {
+ case 0:
+ hex=c<<4;
+ toggle=1;
+ break;
+ case 1:
+ hex+=c;
+ if(hex<32||hex>127)
+ {
+ if(startcnt==1)
+ printf("0x%02X ",(int)hex);
+ else
+ printf(".");
+ }
+ else
+ {
+ printf("%c",hex);
+ if(startcnt==1)
+ printf(" ");
+ }
+ toggle=0;
+ break;
+ }
+ }
+ }
+ printf("\n");
+}
+
+
+
+
+Stack tracing under VM
+----------------------
+A basic backtrace
+-----------------
+
+Here are the tricks I use 9 out of 10 times it works pretty well,
+
+When your backchain reaches a dead end
+--------------------------------------
+This can happen when an exception happens in the kernel & the kernel is entered twice
+if you reach the NULL pointer at the end of the back chain you should be
+able to sniff further back if you follow the following tricks.
+1) A kernel address should be easy to recognise since it is in
+primary space & the problem state bit isn't set & also
+The Hi bit of the address is set.
+2) Another backchain should also be easy to recognise since it is an
+address pointing to another address approximately 100 bytes or 0x70 hex
+behind the current stackpointer.
+
+
+Here is some practice.
+boot the kernel & hit PA1 at some random time
+d g to display the gprs, this should display something like
+GPR 0 = 00000001 00156018 0014359C 00000000
+GPR 4 = 00000001 001B8888 000003E0 00000000
+GPR 8 = 00100080 00100084 00000000 000FE000
+GPR 12 = 00010400 8001B2DC 8001B36A 000FFED8
+Note that GPR14 is a return address but as we are real men we are going to
+trace the stack.
+display 0x40 bytes after the stack pointer.
+
+V000FFED8 000FFF38 8001B838 80014C8E 000FFF38
+V000FFEE8 00000000 00000000 000003E0 00000000
+V000FFEF8 00100080 00100084 00000000 000FE000
+V000FFF08 00010400 8001B2DC 8001B36A 000FFED8
+
+
+Ah now look at whats in sp+56 (sp+0x38) this is 8001B36A our saved r14 if
+you look above at our stackframe & also agrees with GPR14.
+
+now backchain
+d 000FFF38.40
+we now are taking the contents of SP to get our first backchain.
+
+V000FFF38 000FFFA0 00000000 00014995 00147094
+V000FFF48 00147090 001470A0 000003E0 00000000
+V000FFF58 00100080 00100084 00000000 001BF1D0
+V000FFF68 00010400 800149BA 80014CA6 000FFF38
+
+This displays a 2nd return address of 80014CA6
+
+now do d 000FFFA0.40 for our 3rd backchain
+
+V000FFFA0 04B52002 0001107F 00000000 00000000
+V000FFFB0 00000000 00000000 FF000000 0001107F
+V000FFFC0 00000000 00000000 00000000 00000000
+V000FFFD0 00010400 80010802 8001085A 000FFFA0
+
+
+our 3rd return address is 8001085A
+
+as the 04B52002 looks suspiciously like rubbish it is fair to assume that the kernel entry routines
+for the sake of optimisation dont set up a backchain.
+
+now look at System.map to see if the addresses make any sense.
+
+grep -i 0001b3 System.map
+outputs among other things
+0001b304 T cpu_idle
+so 8001B36A
+is cpu_idle+0x66 ( quiet the cpu is asleep, don't wake it )
+
+
+grep -i 00014 System.map
+produces among other things
+00014a78 T start_kernel
+so 0014CA6 is start_kernel+some hex number I can't add in my head.
+
+grep -i 00108 System.map
+this produces
+00010800 T _stext
+so 8001085A is _stext+0x5a
+
+Congrats you've done your first backchain.
+
+
+
+s/390 & z/Architecture IO Overview
+==================================
+
+I am not going to give a course in 390 IO architecture as this would take me quite a
+while & I'm no expert. Instead I'll give a 390 IO architecture summary for Dummies if you have
+the s/390 principles of operation available read this instead. If nothing else you may find a few
+useful keywords in here & be able to use them on a web search engine like altavista to find
+more useful information.
+
+Unlike other bus architectures modern 390 systems do their IO using mostly
+fibre optics & devices such as tapes & disks can be shared between several mainframes,
+also S390 can support upto 65536 devices while a high end PC based system might be choking
+with around 64. Here is some of the common IO terminology
+
+Subchannel:
+This is the logical number most IO commands use to talk to an IO device there can be upto
+0x10000 (65536) of these in a configuration typically there is a few hundred. Under VM
+for simplicity they are allocated contiguously, however on the native hardware they are not
+they typically stay consistent between boots provided no new hardware is inserted or removed.
+Under Linux for 390 we use these as IRQ's & also when issuing an IO command (CLEAR SUBCHANNEL,
+HALT SUBCHANNEL,MODIFY SUBCHANNEL,RESUME SUBCHANNEL,START SUBCHANNEL,STORE SUBCHANNEL &
+TEST SUBCHANNEL ) we use this as the ID of the device we wish to talk to, the most
+important of these instructions are START SUBCHANNEL ( to start IO ), TEST SUBCHANNEL ( to check
+whether the IO completed successfully ), & HALT SUBCHANNEL ( to kill IO ), a subchannel
+can have up to 8 channel paths to a device this offers redunancy if one is not available.
+
+
+Device Number:
+This number remains static & Is closely tied to the hardware, there are 65536 of these
+also they are made up of a CHPID ( Channel Path ID, the most significant 8 bits )
+& another lsb 8 bits. These remain static even if more devices are inserted or removed
+from the hardware, there is a 1 to 1 mapping between Subchannels & Device Numbers provided
+devices arent inserted or removed.
+
+Channel Control Words:
+CCWS are linked lists of instructions initially pointed to by an operation request block (ORB),
+which is initially given to Start Subchannel (SSCH) command along with the subchannel number
+for the IO subsystem to process while the CPU continues executing normal code.
+These come in two flavours, Format 0 ( 24 bit for backward )
+compatibility & Format 1 ( 31 bit ). These are typically used to issue read & write
+( & many other instructions ) they consist of a length field & an absolute address field.
+For each IO typically get 1 or 2 interrupts one for channel end ( primary status ) when the
+channel is idle & the second for device end ( secondary status ) sometimes you get both
+concurrently, you check how the IO went on by issueing a TEST SUBCHANNEL at each interrupt,
+from which you receive an Interruption response block (IRB). If you get channel & device end
+status in the IRB without channel checks etc. your IO probably went okay. If you didn't you
+probably need a doctorto examine the IRB & extended status word etc.
+If an error occurs more sophistocated control units have a facitity known as
+concurrent sense this means that if an error occurs Extended sense information will
+be presented in the Extended status word in the IRB if not you have to issue a
+subsequent SENSE CCW command after the test subchannel.
+
+
+TPI( Test pending interrupt) can also be used for polled IO but in multitasking multiprocessor
+systems it isn't recommended except for checking special cases ( i.e. non looping checks for
+pending IO etc. ).
+
+Store Subchannel & Modify Subchannel can be used to examine & modify operating characteristics
+of a subchannel ( e.g. channel paths ).
+
+Other IO related Terms:
+Sysplex: S390's Clustering Technology
+QDIO: S390's new high speed IO architecture to support devices such as gigabit ethernet,
+this architecture is also designed to be forward compatible with up & coming 64 bit machines.
+
+
+General Concepts
+
+Input Output Processors (IOP's) are responsible for communicating between
+the mainframe CPU's & the channel & relieve the mainframe CPU's from the
+burden of communicating with IO devices directly, this allows the CPU's to
+concentrate on data processing.
+
+IOP's can use one or more links ( known as channel paths ) to talk to each
+IO device. It first checks for path availability & chooses an available one,
+then starts ( & sometimes terminates IO ).
+There are two types of channel path ESCON & the Paralell IO interface.
+
+IO devices are attached to control units, control units provide the
+logic to interface the channel paths & channel path IO protocols to
+the IO devices, they can be integrated with the devices or housed separately
+& often talk to several similar devices ( typical examples would be raid
+controllers or a control unit which connects to 1000 3270 terminals ).
+
+
+ +---------------------------------------------------------------+
+ | +-----+ +-----+ +-----+ +-----+ +----------+ +----------+ |
+ | | CPU | | CPU | | CPU | | CPU | | Main | | Expanded | |
+ | | | | | | | | | | Memory | | Storage | |
+ | +-----+ +-----+ +-----+ +-----+ +----------+ +----------+ |
+ |---------------------------------------------------------------+
+ | IOP | IOP | IOP |
+ |---------------------------------------------------------------
+ | C | C | C | C | C | C | C | C | C | C | C | C | C | C | C | C |
+ ----------------------------------------------------------------
+ || ||
+ || Bus & Tag Channel Path || ESCON
+ || ====================== || Channel
+ || || || || Path
+ +----------+ +----------+ +----------+
+ | | | | | |
+ | CU | | CU | | CU |
+ | | | | | |
+ +----------+ +----------+ +----------+
+ | | | | |
++----------+ +----------+ +----------+ +----------+ +----------+
+|I/O Device| |I/O Device| |I/O Device| |I/O Device| |I/O Device|
++----------+ +----------+ +----------+ +----------+ +----------+
+ CPU = Central Processing Unit
+ C = Channel
+ IOP = IP Processor
+ CU = Control Unit
+
+The 390 IO systems come in 2 flavours the current 390 machines support both
+
+The Older 360 & 370 Interface,sometimes called the paralell I/O interface,
+sometimes called Bus-and Tag & sometimes Original Equipment Manufacturers
+Interface (OEMI).
+
+This byte wide paralell channel path/bus has parity & data on the "Bus" cable
+& control lines on the "Tag" cable. These can operate in byte multiplex mode for
+sharing between several slow devices or burst mode & monopolize the channel for the
+whole burst. Upto 256 devices can be addressed on one of these cables. These cables are
+about one inch in diameter. The maximum unextended length supported by these cables is
+125 Meters but this can be extended up to 2km with a fibre optic channel extended
+such as a 3044. The maximum burst speed supported is 4.5 megabytes per second however
+some really old processors support only transfer rates of 3.0, 2.0 & 1.0 MB/sec.
+One of these paths can be daisy chained to up to 8 control units.
+
+
+ESCON if fibre optic it is also called FICON
+Was introduced by IBM in 1990. Has 2 fibre optic cables & uses either leds or lasers
+for communication at a signaling rate of upto 200 megabits/sec. As 10bits are transferred
+for every 8 bits info this drops to 160 megabits/sec & to 18.6 Megabytes/sec once
+control info & CRC are added. ESCON only operates in burst mode.
+
+ESCONs typical max cable length is 3km for the led version & 20km for the laser version
+known as XDF ( extended distance facility ). This can be further extended by using an
+ESCON director which triples the above mentioned ranges. Unlike Bus & Tag as ESCON is
+serial it uses a packet switching architecture the standard Bus & Tag control protocol
+is however present within the packets. Upto 256 devices can be attached to each control
+unit that uses one of these interfaces.
+
+Common 390 Devices include:
+Network adapters typically OSA2,3172's,2116's & OSA-E gigabit ethernet adapters,
+Consoles 3270 & 3215 ( a teletype emulated under linux for a line mode console ).
+DASD's direct access storage devices ( otherwise known as hard disks ).
+Tape Drives.
+CTC ( Channel to Channel Adapters ),
+ESCON or Paralell Cables used as a very high speed serial link
+between 2 machines. We use 2 cables under linux to do a bi-directional serial link.
+
+
+Debugging IO on s/390 & z/Architecture under VM
+===============================================
+
+Now we are ready to go on with IO tracing commands under VM
+
+A few self explanatory queries:
+Q OSA
+Q CTC
+Q DISK ( This command is CMS specific )
+Q DASD
+
+
+
+
+
+
+Q OSA on my machine returns
+OSA 7C08 ON OSA 7C08 SUBCHANNEL = 0000
+OSA 7C09 ON OSA 7C09 SUBCHANNEL = 0001
+OSA 7C14 ON OSA 7C14 SUBCHANNEL = 0002
+OSA 7C15 ON OSA 7C15 SUBCHANNEL = 0003
+
+If you have a guest with certain priviliges you may be able to see devices
+which don't belong to you to avoid this do add the option V.
+e.g.
+Q V OSA
+
+Now using the device numbers returned by this command we will
+Trace the io starting up on the first device 7c08 & 7c09
+In our simplest case we can trace the
+start subchannels
+like TR SSCH 7C08-7C09
+or the halt subchannels
+or TR HSCH 7C08-7C09
+MSCH's ,STSCH's I think you can guess the rest
+
+Ingo's favourite trick is tracing all the IO's & CCWS & spooling them into the reader of another
+VM guest so he can ftp the logfile back to his own machine.I'll do a small bit of this & give you
+ a look at the output.
+
+1) Spool stdout to VM reader
+SP PRT TO (another vm guest ) or * for the local vm guest
+2) Fill the reader with the trace
+TR IO 7c08-7c09 INST INT CCW PRT RUN
+3) Start up linux
+i 00c
+4) Finish the trace
+TR END
+5) close the reader
+C PRT
+6) list reader contents
+RDRLIST
+7) copy it to linux4's minidisk
+RECEIVE / LOG TXT A1 ( replace
+8)
+filel & press F11 to look at it
+You should see someting like.
+
+00020942' SSCH B2334000 0048813C CC 0 SCH 0000 DEV 7C08
+ CPA 000FFDF0 PARM 00E2C9C4 KEY 0 FPI C0 LPM 80
+ CCW 000FFDF0 E4200100 00487FE8 0000 E4240100 ........
+ IDAL 43D8AFE8
+ IDAL 0FB76000
+00020B0A' I/O DEV 7C08 -> 000197BC' SCH 0000 PARM 00E2C9C4
+00021628' TSCH B2354000 >> 00488164 CC 0 SCH 0000 DEV 7C08
+ CCWA 000FFDF8 DEV STS 0C SCH STS 00 CNT 00EC
+ KEY 0 FPI C0 CC 0 CTLS 4007
+00022238' STSCH B2344000 >> 00488108 CC 0 SCH 0000 DEV 7C08
+
+If you don't like messing up your readed ( because you possibly booted from it )
+you can alternatively spool it to another readers guest.
+
+
+Other common VM device related commands
+---------------------------------------------
+These commands are listed only because they have
+been of use to me in the past & may be of use to
+you too. For more complete info on each of the commands
+use type HELP <command> from CMS.
+detaching devices
+DET <devno range>
+ATT <devno range> <guest>
+attach a device to guest * for your own guest
+READY <devno> cause VM to issue a fake interrupt.
+
+The VARY command is normally only available to VM administrators.
+VARY ON PATH <path> TO <devno range>
+VARY OFF PATH <PATH> FROM <devno range>
+This is used to switch on or off channel paths to devices.
+
+Q CHPID <channel path ID>
+This displays state of devices using this channel path
+D SCHIB <subchannel>
+This displays the subchannel information SCHIB block for the device.
+this I believe is also only available to administrators.
+DEFINE CTC <devno>
+defines a virtual CTC channel to channel connection
+2 need to be defined on each guest for the CTC driver to use.
+COUPLE devno userid remote devno
+Joins a local virtual device to a remote virtual device
+( commonly used for the CTC driver ).
+
+Building a VM ramdisk under CMS which linux can use
+def vfb-<blocksize> <subchannel> <number blocks>
+blocksize is commonly 4096 for linux.
+Formatting it
+format <subchannel> <driver letter e.g. x> (blksize <blocksize>
+
+Sharing a disk between multiple guests
+LINK userid devno1 devno2 mode password
+
+
+
+GDB on S390
+===========
+N.B. if compiling for debugging gdb works better without optimisation
+( see Compiling programs for debugging )
+
+invocation
+----------
+gdb <victim program> <optional corefile>
+
+Online help
+-----------
+help: gives help on commands
+e.g.
+help
+help display
+Note gdb's online help is very good use it.
+
+
+Assembly
+--------
+info registers: displays registers other than floating point.
+info all-registers: displays floating points as well.
+disassemble: dissassembles
+e.g.
+disassemble without parameters will disassemble the current function
+disassemble $pc $pc+10
+
+Viewing & modifying variables
+-----------------------------
+print or p: displays variable or register
+e.g. p/x $sp will display the stack pointer
+
+display: prints variable or register each time program stops
+e.g.
+display/x $pc will display the program counter
+display argc
+
+undisplay : undo's display's
+
+info breakpoints: shows all current breakpoints
+
+info stack: shows stack back trace ( if this dosent work too well, I'll show you the
+stacktrace by hand below ).
+
+info locals: displays local variables.
+
+info args: display current procedure arguments.
+
+set args: will set argc & argv each time the victim program is invoked.
+
+set <variable>=value
+set argc=100
+set $pc=0
+
+
+
+Modifying execution
+-------------------
+step: steps n lines of sourcecode
+step steps 1 line.
+step 100 steps 100 lines of code.
+
+next: like step except this will not step into subroutines
+
+stepi: steps a single machine code instruction.
+e.g. stepi 100
+
+nexti: steps a single machine code instruction but will not step into subroutines.
+
+finish: will run until exit of the current routine
+
+run: (re)starts a program
+
+cont: continues a program
+
+quit: exits gdb.
+
+
+breakpoints
+------------
+
+break
+sets a breakpoint
+e.g.
+
+break main
+
+break *$pc
+
+break *0x400618
+
+heres a really useful one for large programs
+rbr
+Set a breakpoint for all functions matching REGEXP
+e.g.
+rbr 390
+will set a breakpoint with all functions with 390 in their name.
+
+info breakpoints
+lists all breakpoints
+
+delete: delete breakpoint by number or delete them all
+e.g.
+delete 1 will delete the first breakpoint
+delete will delete them all
+
+watch: This will set a watchpoint ( usually hardware assisted ),
+This will watch a variable till it changes
+e.g.
+watch cnt, will watch the variable cnt till it changes.
+As an aside unfortunately gdb's, architecture independent watchpoint code
+is inconsistent & not very good, watchpoints usually work but not always.
+
+info watchpoints: Display currently active watchpoints
+
+condition: ( another useful one )
+Specify breakpoint number N to break only if COND is true.
+Usage is `condition N COND', where N is an integer and COND is an
+expression to be evaluated whenever breakpoint N is reached.
+
+
+
+User defined functions/macros
+-----------------------------
+define: ( Note this is very very useful,simple & powerful )
+usage define <name> <list of commands> end
+
+examples which you should consider putting into .gdbinit in your home directory
+define d
+stepi
+disassemble $pc $pc+10
+end
+
+define e
+nexti
+disassemble $pc $pc+10
+end
+
+
+Other hard to classify stuff
+----------------------------
+signal n:
+sends the victim program a signal.
+e.g. signal 3 will send a SIGQUIT.
+
+info signals:
+what gdb does when the victim receives certain signals.
+
+list:
+e.g.
+list lists current function source
+list 1,10 list first 10 lines of curret file.
+list test.c:1,10
+
+
+directory:
+Adds directories to be searched for source if gdb cannot find the source.
+(note it is a bit sensititive about slashes )
+e.g. To add the root of the filesystem to the searchpath do
+directory //
+
+
+call <function>
+This calls a function in the victim program, this is pretty powerful
+e.g.
+(gdb) call printf("hello world")
+outputs:
+$1 = 11
+
+You might now be thinking that the line above didn't work, something extra had to be done.
+(gdb) call fflush(stdout)
+hello world$2 = 0
+As an aside the debugger also calls malloc & free under the hood
+to make space for the "hello world" string.
+
+
+
+hints
+-----
+1) command completion works just like bash
+( if you are a bad typist like me this really helps )
+e.g. hit br <TAB> & cursor up & down :-).
+
+2) if you have a debugging problem that takes a few steps to recreate
+put the steps into a file called .gdbinit in your current working directory
+if you have defined a few extra useful user defined commands put these in
+your home directory & they will be read each time gdb is launched.
+
+A typical .gdbinit file might be.
+break main
+run
+break runtime_exception
+cont
+
+
+stack chaining in gdb by hand
+-----------------------------
+This is done using a the same trick described for VM
+p/x (*($sp+56))&0x7fffffff get the first backchain.
+
+For z/Architecture
+Replace 56 with 112 & ignore the &0x7fffffff
+in the macros below & do nasty casts to longs like the following
+as gdb unfortunately deals with printed arguments as ints which
+messes up everything.
+i.e. here is a 3rd backchain dereference
+p/x *(long *)(***(long ***)$sp+112)
+
+
+this outputs
+$5 = 0x528f18
+on my machine.
+Now you can use
+info symbol (*($sp+56))&0x7fffffff
+you might see something like.
+rl_getc + 36 in section .text telling you what is located at address 0x528f18
+Now do.
+p/x (*(*$sp+56))&0x7fffffff
+This outputs
+$6 = 0x528ed0
+Now do.
+info symbol (*(*$sp+56))&0x7fffffff
+rl_read_key + 180 in section .text
+now do
+p/x (*(**$sp+56))&0x7fffffff
+& so on.
+
+Disassembling instructions without debug info
+---------------------------------------------
+gdb typically compains if there is a lack of debugging
+symbols in the disassemble command with
+"No function contains specified address." to get around
+this do
+x/<number lines to disassemble>xi <address>
+e.g.
+x/20xi 0x400730
+
+
+
+Note: Remember gdb has history just like bash you don't need to retype the
+whole line just use the up & down arrows.
+
+
+
+For more info
+-------------
+From your linuxbox do
+man gdb or info gdb.
+
+core dumps
+----------
+What a core dump ?,
+A core dump is a file generated by the kernel ( if allowed ) which contains the registers,
+& all active pages of the program which has crashed.
+From this file gdb will allow you to look at the registers & stack trace & memory of the
+program as if it just crashed on your system, it is usually called core & created in the
+current working directory.
+This is very useful in that a customer can mail a core dump to a technical support department
+& the technical support department can reconstruct what happened.
+Provided the have an indentical copy of this program with debugging symbols compiled in &
+the source base of this build is available.
+In short it is far more useful than something like a crash log could ever hope to be.
+
+In theory all that is missing to restart a core dumped program is a kernel patch which
+will do the following.
+1) Make a new kernel task structure
+2) Reload all the dumped pages back into the kernels memory managment structures.
+3) Do the required clock fixups
+4) Get all files & network connections for the process back into an identical state ( really difficult ).
+5) A few more difficult things I haven't thought of.
+
+
+
+Why have I never seen one ?.
+Probably because you haven't used the command
+ulimit -c unlimited in bash
+to allow core dumps, now do
+ulimit -a
+to verify that the limit was accepted.
+
+A sample core dump
+To create this I'm going to do
+ulimit -c unlimited
+gdb
+to launch gdb (my victim app. ) now be bad & do the following from another
+telnet/xterm session to the same machine
+ps -aux | grep gdb
+kill -SIGSEGV <gdb's pid>
+or alternatively use killall -SIGSEGV gdb if you have the killall command.
+Now look at the core dump.
+./gdb ./gdb core
+Displays the following
+GNU gdb 4.18
+Copyright 1998 Free Software Foundation, Inc.
+GDB is free software, covered by the GNU General Public License, and you are
+welcome to change it and/or distribute copies of it under certain conditions.
+Type "show copying" to see the conditions.
+There is absolutely no warranty for GDB. Type "show warranty" for details.
+This GDB was configured as "s390-ibm-linux"...
+Core was generated by `./gdb'.
+Program terminated with signal 11, Segmentation fault.
+Reading symbols from /usr/lib/libncurses.so.4...done.
+Reading symbols from /lib/libm.so.6...done.
+Reading symbols from /lib/libc.so.6...done.
+Reading symbols from /lib/ld-linux.so.2...done.
+#0 0x40126d1a in read () from /lib/libc.so.6
+Setting up the environment for debugging gdb.
+Breakpoint 1 at 0x4dc6f8: file utils.c, line 471.
+Breakpoint 2 at 0x4d87a4: file top.c, line 2609.
+(top-gdb) info stack
+#0 0x40126d1a in read () from /lib/libc.so.6
+#1 0x528f26 in rl_getc (stream=0x7ffffde8) at input.c:402
+#2 0x528ed0 in rl_read_key () at input.c:381
+#3 0x5167e6 in readline_internal_char () at readline.c:454
+#4 0x5168ee in readline_internal_charloop () at readline.c:507
+#5 0x51692c in readline_internal () at readline.c:521
+#6 0x5164fe in readline (prompt=0x7ffff810 "\177\81ÿ\81øx\177\81ÿ\81÷\81Ø\177\81ÿ\81øx\81À")
+ at readline.c:349
+#7 0x4d7a8a in command_line_input (prrompt=0x564420 "(gdb) ", repeat=1,
+ annotation_suffix=0x4d6b44 "prompt") at top.c:2091
+#8 0x4d6cf0 in command_loop () at top.c:1345
+#9 0x4e25bc in main (argc=1, argv=0x7ffffdf4) at main.c:635
+
+
+LDD
+===
+This is a program which lists the shared libraries which a library needs,
+Note you also get the relocations of the shared library text segments which
+help when using objdump --source.
+e.g.
+ ldd ./gdb
+outputs
+libncurses.so.4 => /usr/lib/libncurses.so.4 (0x40018000)
+libm.so.6 => /lib/libm.so.6 (0x4005e000)
+libc.so.6 => /lib/libc.so.6 (0x40084000)
+/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
+
+
+Debugging shared libraries
+==========================
+Most programs use shared libraries, however it can be very painful
+when you single step instruction into a function like printf for the
+first time & you end up in functions like _dl_runtime_resolve this is
+the ld.so doing lazy binding, lazy binding is a concept in ELF where
+shared library functions are not loaded into memory unless they are
+actually used, great for saving memory but a pain to debug.
+To get around this either relink the program -static or exit gdb type
+export LD_BIND_NOW=true this will stop lazy binding & restart the gdb'ing
+the program in question.
+
+
+
+Debugging modules
+=================
+As modules are dynamically loaded into the kernel their address can be
+anywhere to get around this use the -m option with insmod to emit a load
+map which can be piped into a file if required.
+
+The proc file system
+====================
+What is it ?.
+It is a filesystem created by the kernel with files which are created on demand
+by the kernel if read, or can be used to modify kernel parameters,
+it is a powerful concept.
+
+e.g.
+
+cat /proc/sys/net/ipv4/ip_forward
+On my machine outputs
+0
+telling me ip_forwarding is not on to switch it on I can do
+echo 1 > /proc/sys/net/ipv4/ip_forward
+cat it again
+cat /proc/sys/net/ipv4/ip_forward
+On my machine now outputs
+1
+IP forwarding is on.
+There is a lot of useful info in here best found by going in & having a look around,
+so I'll take you through some entries I consider important.
+
+All the processes running on the machine have there own entry defined by
+/proc/<pid>
+So lets have a look at the init process
+cd /proc/1
+
+cat cmdline
+emits
+init [2]
+
+cd /proc/1/fd
+This contains numerical entries of all the open files,
+some of these you can cat e.g. stdout (2)
+
+cat /proc/29/maps
+on my machine emits
+
+00400000-00478000 r-xp 00000000 5f:00 4103 /bin/bash
+00478000-0047e000 rw-p 00077000 5f:00 4103 /bin/bash
+0047e000-00492000 rwxp 00000000 00:00 0
+40000000-40015000 r-xp 00000000 5f:00 14382 /lib/ld-2.1.2.so
+40015000-40016000 rw-p 00014000 5f:00 14382 /lib/ld-2.1.2.so
+40016000-40017000 rwxp 00000000 00:00 0
+40017000-40018000 rw-p 00000000 00:00 0
+40018000-4001b000 r-xp 00000000 5f:00 14435 /lib/libtermcap.so.2.0.8
+4001b000-4001c000 rw-p 00002000 5f:00 14435 /lib/libtermcap.so.2.0.8
+4001c000-4010d000 r-xp 00000000 5f:00 14387 /lib/libc-2.1.2.so
+4010d000-40111000 rw-p 000f0000 5f:00 14387 /lib/libc-2.1.2.so
+40111000-40114000 rw-p 00000000 00:00 0
+40114000-4011e000 r-xp 00000000 5f:00 14408 /lib/libnss_files-2.1.2.so
+4011e000-4011f000 rw-p 00009000 5f:00 14408 /lib/libnss_files-2.1.2.so
+7fffd000-80000000 rwxp ffffe000 00:00 0
+
+
+Showing us the shared libraries init uses where they are in memory
+& memory access permissions for each virtual memory area.
+
+/proc/1/cwd is a softlink to the current working directory.
+/proc/1/root is the root of the filesystem for this process.
+
+/proc/1/mem is the current running processes memory which you
+can read & write to like a file.
+strace uses this sometimes as it is a bit faster than the
+rather inefficent ptrace interface for peeking at DATA.
+
+
+cat status
+
+Name: init
+State: S (sleeping)
+Pid: 1
+PPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+Groups:
+VmSize: 408 kB
+VmLck: 0 kB
+VmRSS: 208 kB
+VmData: 24 kB
+VmStk: 8 kB
+VmExe: 368 kB
+VmLib: 0 kB
+SigPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 7fffffffd7f0d8fc
+SigCgt: 00000000280b2603
+CapInh: 00000000fffffeff
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
+
+User PSW: 070de000 80414146
+task: 004b6000 tss: 004b62d8 ksp: 004b7ca8 pt_regs: 004b7f68
+User GPRS:
+00000400 00000000 0000000b 7ffffa90
+00000000 00000000 00000000 0045d9f4
+0045cafc 7ffffa90 7fffff18 0045cb08
+00010400 804039e8 80403af8 7ffff8b0
+User ACRS:
+00000000 00000000 00000000 00000000
+00000001 00000000 00000000 00000000
+00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000
+Kernel BackChain CallChain BackChain CallChain
+ 004b7ca8 8002bd0c 004b7d18 8002b92c
+ 004b7db8 8005cd50 004b7e38 8005d12a
+ 004b7f08 80019114
+Showing among other things memory usage & status of some signals &
+the processes'es registers from the kernel task_structure
+as well as a backchain which may be useful if a process crashes
+in the kernel for some unknown reason.
+
+Some driver debugging techniques
+================================
+debug feature
+-------------
+Some of our drivers now support a "debug feature" in
+/proc/s390dbf see s390dbf.txt in the linux/Documentation directory
+for more info.
+e.g.
+to switch on the lcs "debug feature"
+echo 5 > /proc/s390dbf/lcs/level
+& then after the error occured.
+cat /proc/s390dbf/lcs/sprintf >/logfile
+the logfile now contains some information which may help
+tech support resolve a problem in the field.
+
+
+
+high level debugging network drivers
+------------------------------------
+ifconfig is a quite useful command
+it gives the current state of network drivers.
+
+If you suspect your network device driver is dead
+one way to check is type
+ifconfig <network device>
+e.g. tr0
+You should see something like
+tr0 Link encap:16/4 Mbps Token Ring (New) HWaddr 00:04:AC:20:8E:48
+ inet addr:9.164.185.132 Bcast:9.164.191.255 Mask:255.255.224.0
+ UP BROADCAST RUNNING MULTICAST MTU:2000 Metric:1
+ RX packets:246134 errors:0 dropped:0 overruns:0 frame:0
+ TX packets:5 errors:0 dropped:0 overruns:0 carrier:0
+ collisions:0 txqueuelen:100
+
+if the device doesn't say up
+try
+/etc/rc.d/init.d/network start
+( this starts the network stack & hopefully calls ifconfig tr0 up ).
+ifconfig looks at the output of /proc/net/dev & presents it in a more presentable form
+Now ping the device from a machine in the same subnet.
+if the RX packets count & TX packets counts don't increment you probably
+have problems.
+next
+cat /proc/net/arp
+Do you see any hardware addresses in the cache if not you may have problems.
+Next try
+ping -c 5 <broadcast_addr> i.e. the Bcast field above in the output of
+ifconfig. Do you see any replies from machines other than the local machine
+if not you may have problems. also if the TX packets count in ifconfig
+hasn't incremented either you have serious problems in your driver
+(e.g. the txbusy field of the network device being stuck on )
+or you may have multiple network devices connected.
+
+
+chandev
+-------
+There is a new device layer for channel devices, some
+drivers e.g. lcs are registered with this layer.
+If the device uses the channel device layer you'll be
+able to find what interupts it uses & the current state
+of the device.
+See the manpage chandev.8 &type cat /proc/chandev for more info.
+
+
+
+Starting points for debugging scripting languages etc.
+======================================================
+
+bash/sh
+
+bash -x <scriptname>
+e.g. bash -x /usr/bin/bashbug
+displays the following lines as it executes them.
++ MACHINE=i586
++ OS=linux-gnu
++ CC=gcc
++ CFLAGS= -DPROGRAM='bash' -DHOSTTYPE='i586' -DOSTYPE='linux-gnu' -DMACHTYPE='i586-pc-linux-gnu' -DSHELL -DHAVE_CONFIG_H -I. -I. -I./lib -O2 -pipe
++ RELEASE=2.01
++ PATCHLEVEL=1
++ RELSTATUS=release
++ MACHTYPE=i586-pc-linux-gnu
+
+perl -d <scriptname> runs the perlscript in a fully intercative debugger
+<like gdb>.
+Type 'h' in the debugger for help.
+
+for debugging java type
+jdb <filename> another fully interactive gdb style debugger.
+& type ? in the debugger for help.
+
+
+
+SysRq
+=====
+This is now supported by linux for s/390 & z/Architecture.
+To enable it do compile the kernel with
+Kernel Hacking -> Magic SysRq Key Enabled
+echo "1" > /proc/sys/kernel/sysrq.
+On 390 all commands are prefixed with
+^-
+e.g.
+^-t will show tasks.
+^-? or some unknown command will display help.
+The sysrq key reading is very picky ( I have to type the keys in an
+ xterm session & paste them into the x3270 console )
+& it may be wise to predefine the keys as described in the VM hints above
+
+This is particularly useful for syncing disks unmounting & rebooting
+if the machine gets partially hung.
+
+Read Documentation/sysrq.txt for more info
+
+References:
+===========
+Enterprise Systems Architecture Reference Summary
+Enterprise Systems Architecture Principles of Operation
+Hartmut Penners s390 stack frame sheet.
+IBM Mainframe Channel Attachment a technology brief from a CISCO webpage
+Various bits of man & info pages of Linux.
+Linux & GDB source.
+Various info & man pages.
+CMS Help on tracing commands.
+Linux for s/390 Elf Application Binary Interface
+Linux for z/Series Elf Application Binary Interface ( Both Highly Recommended )
+z/Architecture Principles of Operation SA22-7832-00
+Enterprise Systems Architecture/390 Reference Summary SA22-7209-01 & the
+Enterprise Systems Architecture/390 Principles of Operation SA22-7201-05
+
+Special Thanks
+==============
+Special thanks to Neale Ferguson who maintains a much
+prettier HTML version of this page at
+http://penguinvm.princeton.edu/notes.html#Debug390
+
--- /dev/null
+Channel attached Tape device driver
+
+-----------------------------WARNING-----------------------------------------
+This driver is considered to be EXPERIMENTAL. Do NOT use it in
+production environments. Feel free to test it and report problems back to us.
+-----------------------------------------------------------------------------
+
+The LINUX for zSeries tape device driver manages channel attached tape drives
+which are compatible to IBM 3480 or IBM 3490 magnetic tape subsystems. This
+includes various models of these devices (for example the 3490E).
+
+
+Tape driver features
+
+The device driver supports a maximum of 128 tape devices.
+No official LINUX device major number is assigned to the zSeries tape device
+driver. It allocates major numbers dynamically and reports them on system
+startup.
+Typically it will get major number 254 for both the character device front-end
+and the block device front-end.
+
+The tape device driver needs no kernel parameters. All supported devices
+present are detected on driver initialization at system startup or module load.
+The devices detected are ordered by their subchannel numbers. The device with
+the lowest subchannel number becomes device 0, the next one will be device 1
+and so on.
+
+
+Tape character device front-end
+
+The usual way to read or write to the tape device is through the character
+device front-end. The zSeries tape device driver provides two character devices
+for each physical device -- the first of these will rewind automatically when
+it is closed, the second will not rewind automatically.
+
+The character device nodes are named /dev/rtibm0 (rewinding) and /dev/ntibm0
+(non-rewinding) for the first device, /dev/rtibm1 and /dev/ntibm1 for the
+second, and so on.
+
+The character device front-end can be used as any other LINUX tape device. You
+can write to it and read from it using LINUX facilities such as GNU tar. The
+tool mt can be used to perform control operations, such as rewinding the tape
+or skipping a file.
+
+Most LINUX tape software should work with either tape character device.
+
+
+Tape block device front-end
+
+The tape device may also be accessed as a block device in read-only mode.
+This could be used for software installation in the same way as it is used with
+other operation systems on the zSeries platform (and most LINUX
+distributions are shipped on compact disk using ISO9660 filesystems).
+
+One block device node is provided for each physical device. These are named
+/dev/btibm0 for the first device, /dev/btibm1 for the second and so on.
+You should only use the ISO9660 filesystem on LINUX for zSeries tapes because
+the physical tape devices cannot perform fast seeks and the ISO9660 system is
+optimized for this situation.
+
+
+Tape block device example
+
+In this example a tape with an ISO9660 filesystem is created using the first
+tape device. ISO9660 filesystem support must be built into your system kernel
+for this.
+The mt command is used to issue tape commands and the mkisofs command to
+create an ISO9660 filesystem:
+
+- create a LINUX directory (somedir) with the contents of the filesystem
+ mkdir somedir
+ cp contents somedir
+
+- insert a tape
+
+- ensure the tape is at the beginning
+ mt -f /dev/ntibm0 rewind
+
+- set the blocksize of the character driver. The blocksize 2048 bytes
+ is commonly used on ISO9660 CD-Roms
+ mt -f /dev/ntibm0 setblk 2048
+
+- write the filesystem to the character device driver
+ mkisofs -o /dev/ntibm0 somedir
+
+- rewind the tape again
+ mt -f /dev/ntibm0 rewind
+
+- Now you can mount your new filesystem as a block device:
+ mount -t iso9660 -o ro,block=2048 /dev/btibm0 /mnt
+
+TODO List
+
+ - Driver has to be stabelized still
+
+BUGS
+
+This driver is considered BETA, which means some weaknesses may still
+be in it.
+If an error occurs which cannot be handled by the code you will get a
+sense-data dump.In that case please do the following:
+
+1. set the tape driver debug level to maximum:
+ echo 6 >/proc/s390dbf/tape/level
+
+2. re-perform the actions which produced the bug. (Hopefully the bug will
+ reappear.)
+
+3. get a snapshot from the debug-feature:
+ cat /proc/s390dbf/tape/hex_ascii >somefile
+
+4. Now put the snapshot together with a detailed description of the situation
+ that led to the bug:
+ - Which tool did you use?
+ - Which hardware do you have?
+ - Was your tape unit online?
+ - Is it a shared tape unit?
+
+5. Send an email with your bug report to:
+ mailto:Linux390@de.ibm.com
+
+
--- /dev/null
+L/390 stand alone dump tools for Linux 2.2.x
+
+-----------------------------WARNING-----------------------------------------
+This driver is considered to be EXPERIMENTAL. Do NOT use it in
+production environments. Feel free to test it and report problems back to us.
+-----------------------------------------------------------------------------
+
+1. Overview
+
+Two stand alone dump tools are provided for generating system memory dumps
+on dasd volumes and tapes: dumpeckd.boot and dumptape.boot
+respectively. The tools should be installed on the device which will
+be used for dumping the system memory; We shall refer to this device as
+dump device. A system memory dump can be initiated by the operator
+at any time. Normally this should be done after a system crash.
+In order to take a dump an ipl of the dump device is required.
+This is destructive which means that the actual running Linux Operating
+System will be killed.
+The ipl process then writes the system memory to the ipl device (either tape
+or dasd).
+
+
+2. Build the dump utilities
+
+The dump tools are compiled with the Kernel:
+> make menuconfig
+> make dep
+> make image
+
+Then under linux/arch/s390/boot the following two files are built:
+- dumpeckd.boot
+- dumptape.boot
+
+
+
+3. Install the Dump utilities
+
+Make sure that there is enough space (memory-size + 1MB) on your dump device
+to hold all your system memory.
+
+3.1 Install DASD dump utility
+
+You have to have an unused dasd partition for dumping. The dump utility is
+installed there and dumps are written afterwards to this dasd. This is how
+to install the dump tool on dasd:
+
+ 1. Format dasd with 4K blocksize:
+ > dasdfmt -f /dev/dasdx -b 4096
+
+ 2. Copy dump utility to dasd (e.g. /dev/dasdx) by issuing the following
+ two commands:
+ > dd if=dumpeckd.boot of=/dev/dasdx
+ > sync
+
+3.2 Install tape dump utility
+
+ 1. Insert empty dump cartridge into your tapedevice (e.g. /dev/ntibm0).
+ 2. Ensure the tape is rewound (e.g mt -f /dev/ntibm0 rewind)
+ 3. Copy dump utility to tape:
+ > dd if=/boot/dumptape.boot of=/dev/ntibm0 bs=4096
+
+
+
+4. Taking the dump
+
+The operator has to do the following tasks in order to take a dump:
+ - Ensure that tape is rewound (if using tapedump.boot)
+ - Stop all cpus (only under VM)
+ - Store status on ipl cpu
+ - IPL the dump tool on ipl cpu
+
+The dump process can take several minutes, depending on the devicetype you are
+using and the amount of your system memory.
+After the dump has completed, the ipl CPU should go into disabled wait.
+
+The following PSW indicates that the dump has been taken successfully:
+
+PSW: 000A0000 00000000
+
+Any other disabled wait PSW indicates an error.
+
+On VM (a three processor machine in this example) this could look like the
+following:
+
+ #cp cpu all stop
+ #cp store status
+ #cp I 193 (if 193 is the dump device)
+ 01: The virtual machine is placed in CP mode due to a SIGP stop from CPU 00.
+ 02: The virtual machine is placed in CP mode due to a SIGP stop from CPU 00.
+ "CP entered; disabled wait PSW 000A0000 00000000"
+ #cp I 192 (the linux dasd)
+
+When initiating the dump process e.g. from a service element check the
+"Store Status" checkbox of the ipl panel.
+
+For detailed information on the actual steps to be performed please consult the
+appropriate manual of your processor.
+
+
+
+5. Copying the dump to a filesystem
+
+After the dump has been created by one of the dump utilities the dump should
+normally be copied over to a filesystem in order to send it e.g.
+to a service organization for analysis.
+
+To copy the dump to a filesystem, you can use command dd as illustrated below:
+
+5.1 Dasd:
+
+Copying the dump from dasd to a filesystem is only necessary, if the dump
+cannot be analyzed locally on the system, but must be e.g. sent to the service
+organization.
+
+- Copy the dump from raw dasd to filesystem (e.g. Dump dasd is /dev/dasdx):
+
+ > dd if=/dev/dasdx1 of=/DUMPDIR/mydump bs=1M \
+ count=<memory size of the crashed system in MB + 1MB>
+
+ or if you want to compress the dump:
+
+ > dd if=/dev/dasdx1 bs=1M count=<memory size in MB + 1MB> | bzip2 \
+ > /DUMPDIR/dump.bz2
+
+5.2 Tape:
+
+ 1. Rewind the tape:
+
+ > mt -f /dev/ntibm0 rewind
+
+ 2. Skip first file on tape (first file is the dump utility itself):
+
+ > mt -f /dev/ntibm0 fsf
+
+ 3. Copy dump from tape to filesystem:
+
+ > dd if=/dev/ntibm0 of=/DUMPDIR/mydump bs=32k
+
+ or if you want to compress the dump:
+
+ > dd if=/dev/ntibm0 bs=32k | bzip2 > /DUMPDIR/mydump.bz2
+
+Note: mt is a tape utility which has be be installed first. You can use
+any other available tape tool to skip the first file.
+
+6. Analyzing the dumps
+
+The dump analysis tool 'lcrash' can be used to analyze the generated dumps.
+The required version of the lkcdutilities is 3.1.2
+(see ftp://oss.sgi.com/projects/lkcd/download/3.1.2/lkcdutils/)
+
+lcrash gets three input files:
+- System.map of the crashed kernel
+- the dump
+- Kerntypes file of the crashed kernel with Kernel type information
+ There is provided a patch for the Kerntypes file under
+ http://oss.software.ibm.com/developerworks/opensource/linux390
+
+Call lcrash:
+> lcrash System.map mydump Kerntypes
+
+Note: If you used dumpeckd.boot to take the dump it is possible to specify
+the /dev/dasdx1 dump device directly as input file to lcrash instead of
+a copy in the filesystem:
+> lcrash System.map /dev/dasdx1 Kerntypes
+
+7. Supported Devices
+
+The dump tools should work with the following devices:
+
+- ECKD Dasds:
+ + 3380
+ + 3390
+
+- Tape units:
+ + 3480
+ + 3490
the port to use for the HotSync transfer. The "Generic" port can be used
for other device communication, such as a PPP link.
+ If after pressing the sync button, nothing shows up in the system log,
+ try resetting the Visor, first a hot reset, and then a cold reset if
+ necessary. Some Visors need this before they can talk to the USB port
+ properly.
+
There is a webpage and mailing lists for this portion of the driver at:
http://usbvisor.sourceforge.net/
Keyspan USA-series Serial Adapters
- Single and Dual port adapters - driver uses Keyspan supplied
+ Single, Dual and Quad port adapters - driver uses Keyspan supplied
firmware and is being developed with their support.
- Driver isn't as far advanced as Keyspan PDA driver mentioned above.
-
Current status:
- Things that work:
- Firmware upload for USA-18X, USA-28, USA-28X, USA-19 and USA-19W
- Simple character I/O fixed at 9600 baud on USA-19 only
-
- Things that don't:
- Everything else. (for now...)
+ The USA-18X, USA-28X, USA-19, USA-19W and USA-49W are supported and
+ have been pretty throughly tested at various baud rates with 8-N-1
+ character settings. Other character lengths and parity setups are
+ presently untested.
+
+ The USA-28 isn't yet supported though doing so should be pretty
+ straightforward. Contact the maintainer if you require this
+ functionality.
+
+ More information is available at:
+ http://www.linuxcare.com.au/hugh/keyspan.html
- Big Things on the todo list:
- Driver is in infancy, much functionality remains to be added
-
FTDI Single Port Serial Driver
(plus a parallel port) and 4 port USB serial converters. The driver
does NOT yet support the Digi AccelePort USB 8.
- This driver works under SMP with the usb-uhci driver. It does not work
- under SMP with the uhci driver.
+ This driver works under SMP with the usb-uhci driver. It does not
+ work under SMP with the uhci driver.
The driver is generally working, though we still have a few more ioctls
to implement and final testing and debugging to do. The paralled port
Belkin USB Serial Adapter F5U103
Single port DB-9/PS-2 serial adapter from Belkin with firmware by eTEK Labs.
+ The Peracom single port serial adapter also works with this driver.
Current status:
The following have been tested and work:
This is an experimental driver to provide connectivity support for the
client synchronization tools for an Empeg empeg-car mp3 player.
+ Tips:
+
+ * Don't forget to create the device nodes for ttyUSB{0,1,2,...}
+ * modprobe empeg (modprobe is your friend)
+ * emptool --usb /dev/ttyUSB0 (or whatever you named your device node)
+
The driver is still pretty new, so some testing 'in the wild' would be
helpful. :)
+MCT USB Single Port Serial Adapter U232
+
+ This driver is for the MCT USB-RS232 Converter (25 pin, Model No.
+ U232-P25) from Magic Control Technology Corp. (there is also a 9 pin
+ Model No. U232-P9). More information about this device can be found
+ at the manufacture's web-site: http://www.mct.com.tw.
+
+ The driver is generally working, though it still needs some more
+ testing. It is derived from the Belkin USB Serial Adapter F5U103
+ driver and its TODO list is valid for this driver as well.
+
+ This driver has also been found to work for other products, which have
+ the same Vendor ID but different Product IDs. Sitecom's U232-P25
+ serial converter uses Product ID 0x230 and Vendor ID 0x711 and works with
+ this driver. Also, D-Link's DU-H3SP USB BAY also works with this driver.
+
+
+Inside Out Networks Edgeport Driver
+
+ This driver supports all devices made by Inside Out Networks, specifically
+ the following models:
+ Edgeport/4
+ Rapidport/4
+ Edgeport/4t
+ Edgeport/2
+ Edgeport/4i
+ Edgeport/2i
+ Edgeport/421
+ Edgeport/21
+ Edgeport/8
+ Edgeport/8 Dual
+ Edgeport/2D8
+ Edgeport/4D8
+ Edgeport/8i
+ Edgeport/2 DIN
+ Edgeport/4 DIN
+ Edgeport/16 Dual
+
+
Generic Serial driver
If your device is not one of the above listed devices, compatible with
To enable the generic driver to recognize your device, build the driver
as a module and load it by the following invocation:
- insmod usb-serial vendor=0x#### product=0x####
+ insmod usbserial vendor=0x#### product=0x####
where the #### is replaced with the hex representation of your device's
vendor id and product id.
W: http://www.cs.uit.no/linux-irda/
S: Maintained
-ISDN SUBSYSTEM (general)
-P: Fritz Elfert
-M: fritz@isdn4linux.de
-L: isdn4linux@listserv.isdn4linux.de
-W: www.isdn4linux.de
-S: Maintained
-
-ISDN SUBSYSTEM (HiSax)
+ISDN SUBSYSTEM
P: Karsten Keil
-M: keil@isdn4linux.de
+M: kkeil@suse.de
+P: Kai Germaschewski
+M: kai.germaschewski@gmx.de
L: isdn4linux@listserv.isdn4linux.de
+W: http://www.isdn4linux.de
S: Maintained
ISDN SUBSYSTEM (Eicon active card driver)
VERSION = 2
PATCHLEVEL = 2
SUBLEVEL = 20
-EXTRAVERSION = pre8
+EXTRAVERSION = pre9
ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/)
ifeq ($(CONFIG_USB),y)
DRIVERS := $(DRIVERS) drivers/usb/usbdrv.o
+else
+ ifdef CONFIG_INPUT_ADBHID
+ DRIVERS := $(DRIVERS) drivers/usb/usbdrv.o
+ endif
endif
ifeq ($(CONFIG_I2O),y)
/*
* We disable IO-APIC IRQs by setting their 'destination CPU mask' to
* zero. Trick by Ramesh Nalluri.
+ * Not anymore. This causes problems on some IO-APIC's, notably AMD 760MP's
+ * So we do it a more 2.4 kind of way now which should be safer -jerdfelt
*/
-DO_ACTION( disable, 1, &= 0x00ffffff, io_apic_sync(entry->apic))/* destination = 0x00 */
-DO_ACTION( enable, 1, |= 0xff000000, ) /* destination = 0xff */
DO_ACTION( mask, 0, |= 0x00010000, io_apic_sync(entry->apic))/* mask = 1 */
DO_ACTION( unmask, 0, &= 0xfffeffff, ) /* mask = 0 */
entry.delivery_mode = dest_LowestPrio;
entry.dest_mode = 1; /* logical delivery */
- entry.mask = 0; /* enable IRQ */
- entry.dest.logical.logical_dest = 0; /* but no route */
+ entry.mask = 1; /* disable IRQ */
+ entry.dest.logical.logical_dest = 0xff;
idx = find_irq_entry(apic,pin,mp_INT);
if (idx == -1) {
static void enable_edge_ioapic_irq(unsigned int irq)
{
self_IPI(irq);
- enable_IO_APIC_irq(irq);
+ unmask_IO_APIC_irq(irq);
}
-static void disable_edge_ioapic_irq(unsigned int irq)
-{
- disable_IO_APIC_irq(irq);
-}
+static void disable_edge_ioapic_irq(unsigned int irq) { /* nothing */ }
/*
* Starting up a edge-triggered IO-APIC interrupt is
pin1 = find_timer_pin(mp_INT);
pin2 = find_timer_pin(mp_ExtINT);
- enable_IO_APIC_irq(0);
+ unmask_IO_APIC_irq(0);
if (!timer_irq_works()) {
if (pin1 != -1)
mainmenu_option next_comment
comment 'Mac device drivers'
-if [ "$CONFIG_INPUT_KEYBDEV" = "y" -o "$CONFIG_INPUT_MOUSEDEV" = "y" ]; then
- bool 'Use input layer for ADB keyboard and mouse' CONFIG_INPUT_ADBHID
-fi
+bool 'Use input layer for ADB keyboard and mouse' CONFIG_INPUT_ADBHID
if [ "$CONFIG_INPUT_ADBHID" = "y" ]; then
- define_bool CONFIG_INPUT_KEYBDEV $CONFIG_VT
- define_bool CONFIG_INPUT_MOUSEDEV y
+ define_bool CONFIG_INPUT_KEYBDEV $CONFIG_INPUT_ADBHID $CONFIG_VT
+ define_bool CONFIG_INPUT_MOUSEDEV $CONFIG_INPUT_ADBHID
define_bool CONFIG_MAC_HID y
bool ' Support for ADB raw keycodes' CONFIG_MAC_ADBKEYCODES
bool ' Support for mouse button 2+3 emulation' CONFIG_MAC_EMUMOUSEBTN
$(OBJCOPY) -O binary $< $@
image: $(CONFIGURE) $(TOPDIR)/vmlinux \
- iplfba.boot ipleckd.boot ipldump.boot
+ iplfba.boot ipleckd.boot dumptape.boot dumpeckd.boot
$(OBJCOPY) -O binary $(TOPDIR)/vmlinux image
$(NM) $(TOPDIR)/vmlinux | grep -v '\(compiled\)\|\( [aU] \)\|\(\.\)\|\(LASH[RL]DI\)' | sort > $(TOPDIR)/System.map
+dumptape.lnk: dumptape.o
+ $(LD) -Ttext 0x2000 -o $@ $<
+
+dumpeckd.boot: dumpeckd.lnk
+ $(OBJCOPY) -O binary $< dumpeckd.boot2
+ dd if=dumpeckd.boot2 > dumpeckd.boot
+ dd if=dumpeckd.boot2 >> dumpeckd.boot
+ rm dumpeckd.boot2
+
+dumpeckd.lnk: dumpeckd.o
+ $(LD) -Ttext 0x2000 -o $@ $<
+
listing: ../../../vmlinux
$(OBJDUMP) --disassemble --disassemble-all --disassemble-zeroes --reloc $(TOPDIR)/vmlinux > listing
dep:
clean:
- rm -f image listing iplfba.boot ipleckd.boot ipldump.boot
+ rm -f image listing iplfba.boot ipleckd.boot dumptape.boot dumpeckd.boot *.lnk *.o
--- /dev/null
+#
+# Common code for the different second stage boot loaders
+# Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+# Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+#
+
+# some definitions from kernel land
+__LC_IPLDEV = 0xC6C
+IPL_DEVICE = 0x10404
+INITRD_START = 0x1040C
+INITRD_SIZE = 0x10414
+COMMAND_LINE = 0x10480
+
+# error codes shown as address in disabled wait psw
+
+EENABLE_DEV = 0x00000100 # enable device failed
+EDISABLE_DEV = 0x00000101 # disable device failed
+ESSCH = 0x00000102 # start subchannel failed
+
+ .macro stage2_start
+#
+# Function entry point at 0x2000 is called with C linkage
+# %r2-%r3: load descriptor
+# %r3 : device subchannel id
+# %r4 : load address
+#
+ basr %r1,0
+ b _load_blocklist-.(%r1)
+ .align 8
+
+#
+# Program execution of the second stage boot loader starts at 0x1008
+#
+ .globl _start
+_start: stm %r0,%r15,0x180 # store all registers
+ basr %r13,0
+0: l %r15,3f-0b(%r13) # load end of stack address
+ l %r11,0xb8 # load ipl device subchannel id
+ lr %r2,%r11
+ bas %r14,_enable_device-0b(%r13)
+ lm %r2,%r3,STAGE2_DESC # get block with load descriptors
+ lr %r4,%r11
+ l %r5,4f-0b(%r13)
+ la %r12,0(%r5) # FIXME: provide the correct offset
+ bas %r14,_load_direct-0b(%r13)
+1: lm %r2,%r5,0(%r12) # load 16 bytes = one load descriptor
+ cl %r4,6f-0b(%r13) # check type range
+ bh 2f
+ sll %r4,2
+ l %r1,5f-0b(%r4,%r13) # function pointer according to type
+ lr %r4,%r11 # pass subchannel id
+ bas %r14,0(%r1)
+2: la %r12,16(%r12) # next load descriptor
+ b 1b-0b(%r13)
+3: .long 0x10000-96
+4: .long 0x8000
+5: .long _load_kernel # type 0: kernel image
+ .long _load_parmfile # type 1: parameter file
+ .long _load_ramdisk # type 2: initial ramdisk
+ .long _jump_target # type 3: jump to target
+6: .long 3 # highest index in list
+
+_load_kernel:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,1f-0b(%r13) # create stack frame
+ lr %r12,%r4
+ bas %r14,_load_blocklist-0b(%r13)
+ st %r12,__LC_IPLDEV # old ipl device storage location
+ l %r6,2f-0b(%r13) # load address of IPL_DEVICE
+ st %r12,0(%r6) # new ipl device storage location
+ l %r6,3f-0b(%r13) # load address of INITRD_START
+ xc 0(4,%r6),0(%r6) # clear INITRD_START
+ l %r6,4f-0b(%r13) # load address of INITRD_SIZE
+ xc 0(4,%r6),0(%r6) # clear INITRD_SIZE
+ lm %r6,%r15,120(%r15)
+ br %r14
+1: .long 96
+2: .long IPL_DEVICE
+3: .long INITRD_START
+4: .long INITRD_SIZE
+
+_load_parmfile: # load parameter file to 0x10480
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,1f-0b(%r13) # create stack frame
+ bas %r14,_load_blocklist-0b(%r13)
+ l %r1,12(%r12)
+ l %r2,2f-0b(%r13) # load address of kernel command line
+ mvc 0x0(256,%r2),0(%r1) # move command line to 0x10480
+ mvc 0x100(256,%r2),0x100(%r1)
+ mvc 0x200(256,%r2),0x200(%r1)
+ mvc 0x300(127,%r2),0x300(%r1)
+ xc 0x37f(1,%r2),0x37f(%r2)
+ lm %r6,%r15,120(%r15)
+ br %r14
+1: .long 96
+2: .long COMMAND_LINE
+
+_load_ramdisk: # load initial ramdisk
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,1f-0b(%r13) # create stack frame
+ lr %r12,%r5 # save load address
+ bas %r14,_load_blocklist-0b(%r13)
+ l %r1,2f-0b(%r13) # ramdisk start storage location
+ st %r12,0(%r1) # store start of ramdisk
+ slr %r2,%r12
+ l %r1,3f-0b(%r13) # ramdisk size storage location
+ st %r2,0(%r1) # store size of ramdisk
+ lm %r6,%r15,120(%r15)
+ br %r14
+1: .long 96
+2: .long INITRD_START
+3: .long INITRD_SIZE
+
+_jump_target:
+ basr %r1,0
+0: lr %r2,%r11
+ lr %r14,%r5 # make branch target return address
+ b _disable_device-0b(%r1)
+
+ .endm
+
+#
+# The load descriptor is 16 bytes in length and contains 3 entries:
+# offset 0 : a blocklist descriptor (fba or eckd)
+# offset 8 : a type
+# offset 12: an address
+# The meaning of the address and the validity of the blocklst
+# depends on the type.
+# type = 0 : kernel image, blocklist valid, address = load address
+# type = 1 : parameter line, blocklist valid, address = load address
+# type = 2 : initial ramdisk, blocklist valid, address = load address
+# type = 3 : jump command, blocklist invalid, address = branch address
+#
+ .macro blocklist_traverser
+# parameter
+# %r2+%r3: blocklist head descriptor
+# %r4 : device subchannel id
+# %r5 : load address
+_load_blocklist:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,4f-0b(%r13) # create stack frame
+1: lr %r12,%r4 # save device subchannel id
+ lr %r11,%r5 # save load address
+ lr %r8,%r2 # save descriptor
+ lr %r9,%r3
+ bas %r14,_extract_length-0b(%r13) # get length from descriptor
+ lr %r10,%r2 # save returned length
+ lr %r2,%r8 # reload descriptor to %r2/%r3
+ lr %r3,%r9
+ lr %r4,%r12 # reload device id to %r4
+ l %r5,6f-0b(%r13) # get memory area for indirect block
+ bas %r14,_load_direct-0b(%r13) # load indirect block
+ lr %r5,%r11 # restore load address
+ lr %r9,%r10 # (length / 8 - 1) = # direct descr.
+ srl %r9,3
+ bctr %r9,0
+ l %r8,6f-0b(%r13)
+2: clc 0(8,%r8),5f-0b(%r13) # test block descriptor
+ be 3f-0b(%r13) # descriptor == 0 -> done
+ lm %r2,%r3,0(%r8) # pass block descriptor
+ lr %r4,%r12 # pass device subchannel id
+ bas %r14,_load_direct-0b(%r13) # load indirect block
+ lr %r5,%r2 # move updated load address
+ la %r8,8(%r8) # next descriptor
+ bct %r9,2b-0b(%r13)
+ lm %r2,%r3,0(%r8) # load continuation descriptor
+ lr %r4,%r12 # move device id for next round
+ clc 0(8,%r8),5f-0b(%r13) # test continuation descriptor
+ bne 1b-0b(%r13) # != 0 -> load next indirect block
+3: lr %r2,%r5 # return updated load address
+ lm %r6,%r15,120(%r15)
+ br %r14
+4: .long 96
+5: .long 0,0
+6: .long 0x8200 # memory address for indirect blocks
+
+ .endm
+
+ .macro device_fn
+#
+# Enable I/O on the ipl device.
+# %r2 : device subchannel id
+#
+_enable_device:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,1f-0b(%r13)
+ lr %r1,%r2
+ l %r2,4f-0b(%r13) # set panik code early
+ stsch 2f-0b(%r13)
+ oi 2f-0b+5(%r13),0x84 # enable ssch and multipath mode
+ msch 2f-0b(%r13)
+ bnz _panik-0b(%r13) # subchannel busy or in error ?
+ lctl %c6,%c6,3f-0b(%r13) # enable all interrupt classes
+ lm %r6,%r15,120(%r15)
+ br %r14
+1: .long 96
+ .align 8
+2: .fill 64,1,0
+3: .long 0xff000000 # CR6 initial value
+4: .long EENABLE_DEV
+
+#
+# Disable I/O on the ipl device.
+# %r2 : device subchannel id
+#
+_disable_device:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,1f-0b(%r13)
+ lr %r1,%r2
+ l %r2,3f-0b(%r13) # set panik code early
+ lctl %c6,%c6,2f-0b(%r13) # disable all interrupt classes
+ stsch 2f-0b(%r13)
+ ni 2f-0b+5(%r13),0x7B # disable ssch and multipath mode
+ msch 2f-0b(%r13)
+ bnz _panik-0b(%r13) # subchannel busy or in error ?
+ lm %r6,%r15,120(%r15)
+ br %r14
+1: .long 96
+ .align 8
+2: .long 0x00000000 # CR6 (all interrupts classes disabled)
+3: .long EDISABLE_DEV
+ .endm
+
+ .macro io_subroutines
+#
+# Start I/O
+# %r2 : device subchannel id
+# %r3 : address of orb
+# %r4 : address of irb
+# %r5 : retry count
+#
+_ssch:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,6f-0b(%r13) # create stack frame
+ lr %r12,%r2 # save subchannel id
+ lr %r11,%r3 # save orb
+ lr %r10,%r4 # save irb
+ lr %r9,%r5 # save retry count
+1: lr %r1,%r12
+ ssch 0(%r11) # go
+ bnz 4f-0b(%r13) # houston, we have a problem
+2: lr %r2,%r12 # call _wait4de with subchannel id
+ lr %r3,%r10 # and irb address as parameters
+ bas %r14,_wait4de-0b(%r13) # wait until DE or error
+ tm 9(%r10),0xff # test channel status
+ bnz 4f-0b(%r13)
+ tm 8(%r10),0xf3 # test device status
+ bz 5f-0b(%r13)
+ bct %r9,1b-0b(%r13) # something went wrong, retry.
+4: l %r2,7f-0b(%r13)
+ bas %r4,_panik-0b(%r13) # won't return
+5: lm %r6,%r15,120(%r15)
+ br %r14
+6: .long 96
+7: .long ESSCH
+
+#
+# Wait for interrupt subroutine
+# %r2 : device subchannel id
+# %r3 : address of irb
+#
+_wait4de:
+ lr %r1,%r2
+ basr %r4,0
+0: mvc 0x78(8),5f-0b(%r4) # set i/o new psw
+1: lpsw 4f-0b(%r4)
+2: c %r1,0xb8 # compare subchannel id
+ bne 1b-0b(%r4) # unequal -> continue waiting
+ tsch 0(%r3)
+ tm 9(%r3),0xff # test channel status
+ bnz 3f-0b(%r4)
+ tm 8(%r3),0xf3 # got something unusual ?
+ bnz 3f-0b(%r4)
+ tm 8(%r3),0x04 # got device end ?
+ bz 1b-0b(%r4) # still busy -> continue waiting
+3: br %r14
+ .align 8
+4: .long 0x020a0000,0x80000000+1b
+5: .long 0x00080000,0x80000000+2b # io new psw
+
+#
+# Panik routine. Loads a disabled wait psw
+# %r2 : panik code
+#
+_panik:
+ basr %r1,0
+0: st %r2,1f-0b+4(%r1) # store code in address part of psw
+ lpsw 1f-0b(%r1)
+ .align 8
+1: .long 0x000a0000,0x00000000
+ .endm
--- /dev/null
+/*
+ * dumpcommon.S
+ *
+ * Common routines for dump records
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Michael Holzheu <holzheu@de.ibm.com>
+ *
+ * Uses extern functions:
+ * - _dump_mem (device dependent function to write dump)
+ *
+ * Functions:
+ * - _take_dump
+ */
+
+
+.include "common.S"
+
+/* General defines */
+#define PAGE_SIZE 0x1000 /* 4096 */
+#define HEADER_SIZE 0x1000 /* 4096 */
+#define END_MARKER_SIZE 0x10
+#define DUMP_END_MARKER 0x44554d50,0x5f454e44 /* DUMP_END */
+#define IPL_SC 0xb8 /* Address of ipl subchannel */
+#define S390_DUMP_MAGIC 0xa8190173,0x618f23fd /* magic number */
+#define ARCH_S390 0x1 /* arch flag for s390 */
+
+/* Error codes */
+#define OK 0x00000000 /* Dump completed successfully */
+#define EMEM 0x00000001 /* Device too small for dump */
+#define EDEV_INVAL 0x00000002 /* Device not supported */
+
+################################################################################
+# Take the dump
+################################################################################
+
+.macro dump_common_fn:
+
+_take_dump:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0
+.Lbase: s %r15,.Lc96-.Lbase(%r13) # create stack frame
+ bas %r14,_store_status-.Lbase(%r13) # store status
+ bas %r14,_count_mem-.Lbase(%r13) # count memory
+ l %r14,.Ldump_mem-.Lbase(%r13)
+ basr %r14,%r14 # dump memory
+ la %r2,OK
+ bas %r14,_panik-.Lbase(%r13) # everything ok: stop now
+
+################################################################################
+# Find out memory size:
+# When accessing a page which is not there, we get a program check
+################################################################################
+
+_count_mem:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+ slr %r9,%r9 # base register for zero page
+ mvc 104(8,%r9),.Lcount_mem_psw-0b(%r13) # setup program check new psw
+ slr %r10,%r10
+ la %r11,1
+ sll %r11,20 # 1 MB
+.Lloop0:
+ l %r12,0(%r10) # test page
+ ar %r10,%r11 # add 1M
+ bnm .Lloop0-0b(%r13) # r10 < 0x80000000 -> loop
+.Lchkmem0:
+ n %r10,.L4malign0-0b(%r13) # align to multiples of 4M
+ st %r10,.Ldh_mem_size+4-0b(%r13) # store memory size
+ st %r10,.Ldh_mem_end+4-0b(%r13) # store memory end
+ srl %r10,12 # calculate page count (/ 4096)
+ st %r10,.Ldh_num_pages-0b(%r13) # store page count
+ mvc 88(32,%r9),.Lnew_psws-0b(%r13) # restore disabled wait new psws
+ lm %r6,%r15,120(%r15)
+ br %r14
+.Lcount_mem_psw: .long 0x00080000,0x80000000 + .Lchkmem0
+.Lnew_psws:
+.long 0x000a0000,0x00000058 # external new psw
+.long 0x000a0000,0x00000060 # svc new psw
+.long 0x000a0000,0x00000068 # program check new psw
+.long 0x000a0000,0x00000070 # machine check new psw
+.L4malign0:
+.long 0xffc00000
+
+################################################################################
+# store status of all cpus in their lowcores
+################################################################################
+
+
+_store_status:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13)
+ la %r7,0x0 # base register for 0 page
+
+ ######## move lowcore info (assume user has made store ########
+ ######## status) to prefix-page ########
+
+ lr %r2,%r7 # zero page (source)
+ bas %r14,_copy_lowcore-0b(%r13)
+
+ ######## stop all cpus and store status in prefix pages ########
+
+.Lstore_all:
+ la %r8,0 # first cpu
+ stap .Lcurrent_cpu+2-0b(%r13) # store current cpu address
+
+.Lstatus_loop:
+ cl %r8,.Lcurrent_cpu-0b(%r13) # is ipl cpu ?
+ be .Lnext_cpu-0b(%r13) # if yes get next cpu
+.Lstop_loop:
+ sigp %r9,%r8,0x5 # stop cpu
+ bc 8,.Lcpu_stopped-0b(%r13) # accepted
+ bc 4,.Lnext_cpu-0b(%r13) # status stored in register:
+ # next cpu
+ bc 2,.Lstop_loop-0b(%r13) # busy: try again
+ bc 1,.Lnext_cpu-0b(%r13) # not op: next cpu
+.Lcpu_stopped:
+ lr %r9,%r7
+ sigp %r9,%r8,0xe # store status of cpu
+ bc 8,.Lcpu_stored-0b(%r13) # accepted
+ bc 4,.Lnext_cpu-0b(%r13) # status stored: next cpu
+ bc 2,.Lcpu_stopped-0b(%r13) # busy: try again
+ bc 1,.Lnext_cpu-0b(%r13) # not op: next cpu
+.Lcpu_stored:
+ lr %r2,%r7 # zero page (source)
+ bas %r14,_copy_lowcore-0b(%r13)
+.Lnext_cpu:
+ la %r8,1(%r8) # next cpu (r8 +=1)
+ cl %r8,.Llast_cpu-0b(%r13) # is last possible cpu ?
+ bl .Lstatus_loop-0b(%r13) # jump if not last cpu
+.Lstore_status_exit:
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+.Lcurrent_cpu:
+ .long 0x0
+.Llast_cpu:
+ .long 0x0000ffff
+
+############1###################################################################
+# copy lowcore to prefix page
+# r2: address of source lowcore (input by caller)
+################################################################################
+
+_copy_lowcore:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13)
+
+ l %r3,0x108(%r2) # get prefix page from lowcore
+
+ ###### check if lowcore address looks valid ######
+
+ cl %r3,.Llinux_start-0b(%r13) # looks valid ?
+ bl .Lcpy_locore_exit-0b(%r13) # if < linux-start addr
+ l %r6,.Lpage_align-0b(%r13) # check page alignment
+ nr %r3,%r6
+ cl %r3,0x108(%r2)
+ bnz .Lcpy_locore_exit-0b(%r13) # if not page aligned
+
+ ###### copy lowcore ######
+
+ # |-----------------------------------------------------------|
+ # | Decimal | Length | Data |
+ # | Address | in Bytes | |
+ # |_________|___________|_____________________________________|
+ # | 212 | 4 | Extended save area address |
+ # | 216 | 8 | CPU timer |
+ # | 224 | 8 | Clock comparator |
+ # | 256 | 8 | Current PSW |
+ # | 264 | 4 | Prefix register |
+ # | 288 | 64 | Access registers 0 through 15 |
+ # | 352 | 32 | Floating-point registers 0 through 6|
+ # | 384 | 64 | General registers 0 through 15 |
+ # | 448 | 64 | Control registers 0 through 15 |
+ # |_________|___________|_____________________________________|
+
+ mvc 212(20,%r3),212(%r2)
+ mvc 256(12,%r3),256(%r2)
+ mvc 288(224,%r3),288(%r2)
+
+.Lcpy_locore_exit:
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+.Lpage_align:
+ .long -4096
+
+.align 4
+.Ldump_mem: .long _dump_mem # address of function
+
+.Llinux_start:
+ .long 0x10000
+.Lc96: .long 96 # for creating stackframes
+.align 8
+#
+# The Dump header
+#
+.Ldh_dumpheader:
+.Ldh_magic_number:.long S390_DUMP_MAGIC
+.Ldh_version: .long 0x00000001
+.Ldh_header_size: .long HEADER_SIZE
+.Ldh_dump_level: .long 0x00000004 # DUMP_ALL
+.Ldh_page_size: .long PAGE_SIZE
+.Ldh_mem_size: .long 0x00000000,0x00000000
+.Ldh_mem_start: .long 0x00000000,0x00000000
+.Ldh_mem_end: .long 0x00000000,0x00000000
+.Ldh_num_pages: .long 0x00000000
+.Ldh_pad: .long 0x00000000
+.Ldh_time: .long 0x00000000,0x00000000
+.Ldh_cpuid: .long 0x00000000,0x00000000
+.Ldh_arch: .long ARCH_S390
+.Ldh_free: .long 0x00000000
+#
+# Dump End Marker
+#
+.align 8
+.Ld_endmarker: .long DUMP_END_MARKER
+.Ld_end_time: .long 0x00000000,0x00000000
+
+.endm
--- /dev/null
+/*
+ * Dump boot loader for 3380/3390 DASDs
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Michael Holzheu <holzheu@de.ibm.com>
+ * Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *
+ * Uses extern functions:
+ * - _panik
+ * - _enable_device
+ * - _take_dump
+ *
+ * Functions:
+ * - _dump_mem
+ */
+
+#include "dumpcommon.S"
+
+/* General defines */
+
+#define IPL_BS 0x1000
+#define BLOCKS_PER_WRITE 64 /* makes 256K with 4K blksize */
+
+#ifdef ZIPL
+
+################################################################################
+# Function entry point at 0x2000 (not used for dump) is called with C linkage
+# %r2-%r3: load descriptor
+# %r3 : device subchannel id
+# %r4 : load address
+################################################################################
+
+ basr %r1,0
+ b _not_implemented-.(%r1)
+ .align 8
+
+#else
+
+#define DUMP_TOOL_START 0x2000
+
+################################################################################
+# one psw and two ccws:
+# Re-Read enough of bootsector to start
+################################################################################
+
+.psw: .long 0x00080000,0x80000000+_start
+.ccw1: .long 0x06000000 + DUMP_TOOL_START,0x00000000 + IPL_BS
+.ccw2: .long 0x00000000,0x00000000
+
+#endif /* ZIPL */
+
+################################################################################
+# Program execution of the second stage boot loader starts at 0x2008
+################################################################################
+
+ .globl _start
+_start: basr %r13,0
+0: l %r15,1f-0b(%r13) # load end of stack address
+ l %r11,IPL_SC # load ipl device subchannel id
+ lr %r2,%r11
+ l %r14,.Lenable_device-0b(%r13)
+ basr %r14,%r14
+ bas %r14,_get_device_characteristics-0b(%r13)
+ l %r5,.Ldev_end_rec-0b(%r13) # endblock (first block = nr 1)
+ st %r5,.Ldev_bpt-0b(%r13)
+ l %r5,.Ldev_blk_size-0b(%r13) # get blocksize
+ stcm %r5,3,.Lwrccw+2-0b(%r13) # store blocksize into wr template
+ stcm %r5,3,.Llodata+14-0b(%r13) # store blocksize into lodata
+ l %r14,.Ltake_dump-0b(%r13)
+ basr %r14,%r14
+1: .long 0x10000-96 # end of stack
+
+#ifdef ZIPL
+
+################################################################################
+# Get the device characteristics:
+#
+# Get device characteristics from zipl parameters (at 0x218)
+# The following parameter format is expected 2x8 byte:
+#
+# Dump Start: CCHH RBBN
+# Dump End: CCHH RBBX
+#
+# - CC: Start/End Cylinder Number
+# - HH: Start/End Head Number
+# - R : Start/End Record Number
+# - BB: Blocksize
+# - N : Number of Heads of DASD
+# - X : Unused
+#
+# Cylinder, Heads are counted starting with 0.
+# Records are counted starting with 1.
+# We assume that the End Record Number is at track boundary.
+# This allows us to determine the number of Blocks Per Track.
+################################################################################
+
+_get_device_characteristics:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+ la %r12,0 # load base register
+ mvc .Ldev_start_cyl-0b+2(2,%r13),0x218(%r12)
+ mvc .Ldev_start_hd-0b+2(2,%r13),0x21a(%r12)
+ mvc .Ldev_start_rec-0b+3(1,%r13),0x21c(%r12)
+ mvc .Ldev_blk_size-0b+2(2,%r13),0x21d(%r12)
+ mvc .Ldev_nr_hds-0b+3(1,%r13),0x21f(%r12)
+ mvc .Ldev_end_cyl-0b+2(2,%r13),0x220(%r12)
+ mvc .Ldev_end_hd-0b+2(2,%r13),0x222(%r12)
+ mvc .Ldev_end_rec-0b+3(1,%r13),0x224(%r12)
+ lm %r6,%r15,120(%r15)
+ br %r14
+
+#else
+
+################################################################################
+# Get the device characteristics:
+#
+# The following is fix:
+# - blocksize = 4K
+# - start cylinder = 0
+# - start head = 0
+# - start record = 4
+# - end cylinder = unlimited
+# - end head = nr of heads
+# - end record = blocks per track
+#
+# The following is read from the device characteristics
+#
+# - model 3380/90 ==> blocks per track
+# - nr of heads
+#
+################################################################################
+
+_get_device_characteristics:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+ la %r6,.Lrdcccw-0b(%r13)
+ st %r6,.Lorb+8-0b(%r13) # store cp-address to orb
+ l %r2,IPL_SC
+ la %r3,.Lorb-0b(%r13)
+ la %r4,.Lirb-0b(%r13)
+ la %r5,2
+ bas %r14,_ssch-0b(%r13) # start I/O: Read device characteristic
+
+ # find out blocks per track (bpt)
+
+ la %r6,9
+ clc .Lrdcdata+3-0b(2,%r13),.L9345-0b(%r13)
+ be 1f-0b(%r13)
+ la %r6,10
+ clc .Lrdcdata+3-0b(2,%r13),.L3380-0b(%r13)
+ be 1f-0b(%r13)
+ la %r6,12
+ clc .Lrdcdata+3-0b(2,%r13),.L3390-0b(%r13)
+ be 1f-0b(%r13)
+
+ # not supported device panik
+
+ la %r2,EDEV_INVAL
+ l %r14,.Lpanik-0b(%r13)
+ basr %r14,%r14
+
+1:
+ # store dev characteristic
+
+ st %r6,.Ldev_end_rec-0b(%r13)
+
+ slr %r6,%r6
+ icm %r6,3,.Lrdcdata+14-0b(%r13)
+ st %r6,.Ldev_end_hd-0b(%r13)
+ st %r6,.Ldev_nr_hds-0b(%r13)
+
+ la %r6,0
+ st %r6,.Ldev_start_cyl-0b(%r13)
+ la %r6,0
+ st %r6,.Ldev_start_hd-0b(%r13)
+ la %r6,4
+ st %r6,.Ldev_start_rec-0b(%r13)
+ l %r6,.Lblk_size-0b(%r13)
+ st %r6,.Ldev_blk_size-0b(%r13)
+ l %r6,.Lend_cyl-0b(%r13)
+ st %r6,.Ldev_end_cyl-0b(%r13)
+
+ lm %r6,%r15,120(%r15)
+ br %r14
+.L3390:
+ .word 0x3390
+.L9345:
+ .word 0x9345
+.L3380:
+ .word 0x3380
+.Lend_cyl:
+ .long 0x0000ffff
+.Lblk_size:
+ .long 0x1000
+.align 8
+
+.Lrdcdata:
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+.Lrdcccw:
+ .long 0x64000040,0x00000000+.Lrdcdata # read device characteristics
+
+#endif /* ZIPL */
+
+################################################################################
+# Dump memory
+################################################################################
+
+_dump_mem:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+
+ # calculate start and end block
+
+ la %r6,0
+ l %r7,.Ldev_start_cyl-0b(%r13)
+ la %r8,0
+ l %r9,.Ldev_start_hd-0b(%r13)
+ l %r10,.Ldev_start_rec-0b(%r13)
+ l %r11,.Ldev_nr_hds-0b(%r13)
+ l %r12,.Ldev_bpt-0b(%r13) # = blocks per track
+
+ # calculate start block
+ #######################
+
+ # cyl * nr_hds * blkptr
+
+ mr %r6,%r11
+ mr %r6,%r12
+
+ # hd * recs
+
+ mr %r8,%r12
+
+ # start block = cyl * nr_hds * bptr + hd * bptr + rec
+
+ ar %r7,%r9
+ ar %r7,%r10
+
+ bctr %r7,0 # decrement, we start counting
+ # beginning with block 0
+
+ st %r7,.Ldev_start_blk-0b(%r13)
+
+
+
+ # calculate end block
+ #######################
+
+ l %r7,.Ldev_end_cyl-0b(%r13)
+ l %r9,.Ldev_end_hd-0b(%r13)
+
+ # cyl * nr_hds * blkptr
+
+ mr %r6,%r11
+ mr %r6,%r12
+
+ # hd * recs
+
+ mr %r8,%r12
+
+ # end block = cyl * nr_hds * bptr + hd * bptr + rec
+
+ ar %r7,%r9
+ ar %r7,%r12
+
+ bctr %r7,0 # decrement, we start counting
+ # beginning with block 0
+
+ st %r7,.Ldev_end_blk-0b(%r13)
+
+ # calculate bytes per write (blksize * blwr)
+
+ l %r11,.Ldev_blk_size-0b(%r13)
+ mh %r11,.Lblocks_per_write-0b(%r13)
+ st %r11,.Lbytes_per_write-0b(%r13)
+
+# write header
+
+.Lheaders: # write dump headers
+ stck .Ldh_time-0b(%r13) # store time
+ stidp .Ldh_cpuid-0b(%r13) # store cpu id
+
+ l %r11,.Ldev_start_blk-0b(%r13) # start block
+
+ lr %r2,%r11
+ la %r3,.Ldh_dumpheader-0b(%r13) # address of dump header
+
+ l %r4,.Lheader_size-0b(%r13)
+ srda %r4,32 # shift ==> 64 bit number
+ l %r6,.Ldev_blk_size-0b(%r13) # get blocksize
+
+ dr %r4,%r6 # nr of blocks for header =
+ # HEADER_SIZE / BLOCKSIZE = r5
+ lr %r4,%r5
+ lr %r12,%r5 # save nr of blocks
+ bas %r14,_writeblock-0b(%r13) # write block to disk
+ ar %r11,%r12 # update block counter
+
+.Lmemory: # write memory
+
+# write memory
+
+ la %r10,0 # start at address 0
+
+.Lmloop:
+ la %r4,BLOCKS_PER_WRITE # write so many blocks at a time
+ lr %r2,%r11 # restore r2
+ lr %r3,%r10 # restore r3
+ bas %r14,_writeblock-0b(%r13) # write block to disk
+ l %r2,.Lbytes_per_write-0b(%r13)
+ ar %r10,%r2 # update data address
+ ah %r11,.Lblocks_per_write-0b(%r13) # skip to next block
+ l %r3,.Ldh_mem_size+4-0b(%r13) # get memsize
+ clr %r10,%r3 # enough ?
+ bl .Lmloop-0b(%r13) # branch if r10 < r3
+
+# write end marker
+
+.lendmarker: # terminate dump file
+ la %r4,1 # write endmaker with one block
+ stck .Ld_end_time-0b(%r13) # store end time
+ lr %r2,%r11 # restore r2
+ la %r3,.Ld_endmarker-0b(%r13) # address of dump end marker
+ la %r4,1 # write 4k at a time
+ bas %r14,_writeblock-0b(%r13) # write block to disk
+
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+.Lbytes_per_write: .long 0x00000000
+.Lheader_size: .long HEADER_SIZE
+.Lblocks_per_write: .word BLOCKS_PER_WRITE
+
+################################################################################
+# This function writes a block number given in r2 to disk
+# r2: number of first block to write ( input by caller )
+# We start counting with Block Nr 0 !!!
+# r3: address to write data from ( input by caller )
+# r4: number of blocks to write ( input by caller )
+################################################################################
+
+_writeblock:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+
+ # check if blocks are within range:
+
+ lr %r11,%r2
+ ar %r11,%r4 # End block
+ l %r12,.Ldev_end_blk-0b(%r13)
+ clr %r11,%r12 # End block < dev_end_blk ?
+ bl 1f-0b(%r13) # no
+ la %r2,EMEM # if yes panik
+ l %r14,.Lpanik-0b(%r13)
+ basr %r14,%r14
+
+1: la %r12,.Ldeccw-0b(%r13)
+ st %r12,8+.Lorb-0b(%r13) # store cpaddr to orb
+ la %r12,.Lwrccw-0b(%r13)
+ oi 1(%r12),0x40 # set CC in wr template
+
+ # first setup the write channel program
+
+ lr %r11,%r4 # save number of blocks
+
+ l %r6,.Ldev_bpt-0b(%r13) # load bpt to r6
+ l %r7,.Ldev_nr_hds-0b(%r13) # load heads to r7
+ la %r5,.Lwrloop-0b(%r13)
+ br %r5 /* FIXME */
+
+2: # loop for number of block (nbl) time
+
+ # switch to next write CCW
+
+ l %r5,.Ldev_blk_size-0b(%r13)
+ ar %r3,%r5 # add blksize to destination addr
+ ah %r12,.Lccw_size-0b(%r13) # add sizeof(ccw) to base address
+ mvc 0(8,%r12),.Lwrccw-0b(%r13) # copy template to this ccw
+
+.Lwrloop:
+
+ # build up next write CCW
+
+ st %r3,4(%r12) # store target addr to this ccw
+ bct %r4,2b-0b(%r13) # decrement no of blks still to do
+ ni 1(%r12),0x3f # no command chaining for last ccw
+
+ # write CCWs are setup now
+
+ lr %r4,%r11 # restore number of blocks
+ stcm %r4,3,.Llodata+2-0b(%r13) # store number of blocks to lodata
+
+ # compute end block
+
+ ar %r4,%r2 # r4: ebl = blk + nbl
+ bctr %r4,0 # decrement r4 (last blk touched)
+
+ # compute start track and start block on track
+
+ srda %r2,32 # shift ==> 64 bit number
+ dr %r2,%r6 # trk = blk / bpt, bot = blk % bpt
+ # r3: trk, r2: bot
+ la %r2,1(%r2) # bot++ ( we start counting at 1 )
+ stcm %r2,1,.Llodata+12-0b(%r13) # store bot to lodata
+
+ # compute start cylinder and head
+
+ xr %r2,%r2 # clear bot
+ dr %r2,%r7 # cy=trk / heads, hd=trk % heads
+ # r3: cy, r2: hd
+ sll %r3,16 # combine to CCHH in r3
+ or %r3,%r2
+ st %r3,.Ldedata+8-0b(%r13) # store cchh to dedata
+ st %r3,.Llodata+4-0b(%r13) # store cchh to lodata
+ st %r3,.Llodata+8-0b(%r13) # store cchh to lodata
+
+ # compute end track and end block on track
+
+ lr %r11,%r5 # save r5
+ srda %r4,32 # shift ==> 64 bit number
+ dr %r4,%r6 # tr2 = ebl / bpt
+ # r5: tr2, r4: bot2
+ # compute end cylinder and head
+
+ xr %r4,%r4 # cy2=tr2/heads, hd2=hd2 % heads
+ dr %r4,%r7 # r5: cy2, r4: hd2
+ stcm %r5,3,.Ldedata+12-0b(%r13) # store cy2,hd2 to dedata
+ stcm %r4,3,.Ldedata+14-0b(%r13) # store cy2,hd2 to dedata
+ lr %r5,%r11 # restore r5
+
+ # CCWs are setup now, arent they?
+
+ l %r2,IPL_SC # subchannel id
+ la %r3,.Lorb-0b(%r13)
+ la %r4,.Lirb-0b(%r13)
+ la %r5,10 # initialize retries
+
+ bas %r14,_ssch-0b(%r13) # start I/O
+
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+.Lccw_size:
+ .word 0x8
+.align 8
+.Lorb:
+ .long 0x0049504c,0x0080ff00 # intparm is " IPL"
+ .long 0x00000000,0x00000000
+.Lirb:
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+ .long 0x00000000,0x00000000
+.Ldedata:
+ .long 0x80c00000,0x00000000
+ .long 0x00000000,0x00000000
+.Llodata:
+ .long 0x01800001,0x00000000
+ .long 0x00000000,0x03000000
+ .long 0x12345678,0x00000000
+
+################################################################################
+# read function not implemented: return EINVAL
+################################################################################
+
+_not_implemented:
+ la %r2,22 /* EINVAL */
+ lcr %r2,%r2 /* -EINVAL */
+ br %r14
+
+################################################################################
+# expand Macros
+################################################################################
+
+ dump_common_fn
+ device_fn
+ io_subroutines
+
+
+################################################################################
+# DATA
+################################################################################
+
+# extern functions
+
+.Lpanik:
+ .long _panik
+.Lenable_device:
+ .long _enable_device
+.Ltake_dump:
+ .long _take_dump
+
+# device characteristics
+.align 8
+.Ldev_start_cyl:
+ .long 0x00000000
+.Ldev_start_hd:
+ .long 0x00000000
+.Ldev_start_rec:
+ .long 0x00000000
+.Ldev_blk_size:
+ .long 0x00000000
+.Ldev_nr_hds:
+ .long 0x00000000
+.Ldev_end_cyl:
+ .long 0x00000000
+.Ldev_end_hd:
+ .long 0x00000000
+.Ldev_end_rec:
+ .long 0x00000000
+
+
+.Ldev_start_blk:
+ .long 0x00000000
+.Ldev_end_blk:
+ .long 0x00000000
+.Ldev_bpt:
+ .long 0x00000000
+
+
+#
+# CCWs
+#
+
+.align 8
+
+# channel program for one write
+
+.Ldeccw:
+ .long 0x63400010,0x00000000+.Ldedata # define extent
+.Lloccw:
+ .long 0x47400010,0x00000000+.Llodata # locate record
+.Lwrccw:
+ .long 0x8d000000,0x00000000 # update Key & data
+
+.org IPL_BS
--- /dev/null
+/*
+ * Dump boot loader for 3480/3490 tape devices
+ * Copyright (C) 1999-2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Michael Holzheu (holzheu@de.ibm.com),
+ * Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *
+ * Uses extern functions:
+ * - _panik
+ * - _enable_device
+ * - _take_dump
+ *
+ * Functions:
+ * - _dump_mem
+ */
+
+#include "dumpcommon.S"
+
+#define IPL_BS 0x1000
+#define BLOCK_SIZE 0x8000 /* 32 KB */
+#define DUMP_TOOL_START 0x2000 /* tool is loaded to this address in order */
+ /* not to overwrite page 0 */
+
+################################################################################
+# The first 24 bytes are loaded by ipl to addresses 0-23. (a PSW and two CCWs)
+################################################################################
+
+ # psw
+ .long 0x00080000,0x80000000+_start
+ # rewind ccw
+ .long 0x07000000,0x60000001
+ # ccw to load dump utility to 0x1000
+ .long 0x02000000+DUMP_TOOL_START ,0x20000000+IPL_BS
+
+.globl _start
+_start: basr %r13,0
+0: l %r15,1f-0b(%r13) # load end of stack address
+ l %r11,IPL_SC # load ipl device subchannel id
+ lr %r2,%r11
+ l %r14,.Lenable_device-0b(%r13)
+ basr %r14,%r14
+ l %r14,.Ltake_dump-0b(%r13)
+ basr %r14,%r14
+1: .long 0x10000-96 # end of stack
+
+################################################################################
+# Dump memory
+################################################################################
+
+_dump_mem:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+#
+# first write a tape mark
+#
+ bas %r14,_tapemark-0b(%r13)
+#
+# write header
+#
+ stck .Ldh_time-0b(%r13) # store time
+ stidp .Ldh_cpuid-0b(%r13) # store cpu id
+ la %r2,.Ldh_dumpheader-0b(%r13) # start of header
+ l %r3,.Lheader_size-0b(%r13) # size of header
+ lr %r4,%r3 # blocksize
+ bas %r14,_writer-0b(%r13)
+
+#
+# write real storage to tape
+#
+
+ la %r2,0 # start
+ l %r3,.Ldh_mem_size+4-0b(%r13) # length
+ l %r4,.Lblock_size-0b(%r13) # blocksize
+ bas %r14,_writer-0b(%r13) # write page
+#
+# write end marker
+#
+ stck .Ld_end_time-0b(%r13) # store end time
+ la %r2,.Ld_endmarker-0b(%r13) # address of end marker
+ la %r3,END_MARKER_SIZE # size of end marker
+ la %r4,END_MARKER_SIZE # blocksize
+ bas %r14,_writer-0b(%r13)
+#
+# write another tape mark
+#
+ bas %r14,_tapemark-0b(%r13)
+
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+.Lheader_size: .long HEADER_SIZE
+.Lblock_size: .long BLOCK_SIZE
+
+################################################################################
+# subroutine for writing to tape
+# Parameters:
+# -r2: start address
+# -r3: length
+# -r4: blocksize
+################################################################################
+
+_writer:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+
+ lr %r10,%r2 # save start address
+ lr %r11,%r3 # save length
+ ar %r11,%r2 # end address
+ lr %r12,%r4 # save blocksize
+
+ st %r10,.Lccwwrite+4-0b(%r13) # initialize CCW data addresses
+ sth %r12,.Lccwwrite+2-0b(%r13) # setup blocksize
+.Lldlp:
+ l %r2,IPL_SC # subchannel id
+ la %r3,.Lorbwrite-0b(%r13) # address of orb
+ la %r4,.Lirb-0b(%r13) # address of irb
+ la %r5,10 # 10 retries
+ bas %r14,_ssch-0b(%r13) # write chunk of PAGE_SIZE bytes
+
+ l %r0,.Lccwwrite+4-0b(%r13) # update CCW data addresses
+ ar %r0,%r12 # add block size
+ st %r0,.Lccwwrite+4-0b(%r13)
+ clr %r0,%r11 # enough ?
+ bl .Lldlp-0b(%r13)
+
+ lm %r6,%r15,120(%r15)
+ br %r14
+
+################################################################################
+# write tapemark
+################################################################################
+
+_tapemark:
+ stm %r6,%r15,24(%r15)
+ basr %r13,0 # base register
+0: s %r15,.Lc96-0b(%r13) # create stack frame
+
+ l %r2,IPL_SC # subchannel id
+ la %r3,.Lorbmark-0b(%r13) # r12 = address of orb
+ la %r4,.Lirb-0b(%r13) # r5 = address of irb
+ la %r5,10 # retries
+ bas %r14,_ssch-0b(%r13) # write a tape mark
+
+ lm %r6,%r15,120(%r15)
+ br %r14 # return to caller
+
+################################################################################
+# expand Macros
+################################################################################
+
+dump_common_fn
+device_fn
+io_subroutines
+
+################################################################################
+# DATA
+################################################################################
+
+# extern functions
+
+.Lpanik:
+ .long _panik
+.Lenable_device:
+ .long _enable_device
+.Ltake_dump:
+ .long _take_dump
+
+# irbs, orbs etc.
+
+ .align 8
+.Lorbwrite:
+ .long 0x00000000,0x0080ff00,.Lccwwrite
+ .align 8
+.Lorbmark:
+ .long 0x00000000,0x0080ff00,.Lccwmark
+ .align 8
+.Lccwwrite:
+ .long 0x01200000,0x00000000
+.Lccwmark:
+ .long 0x1f200001,0x00000000
+
+.Lirb: .long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+.org IPL_BS
+++ /dev/null
-/*
- * arch/s390/boot/ipldump.S
- *
- * S390 version
- * Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
- *
- * Tape dump ipl record. Put it on a tape and ipl from it and it will
- * write a dump of the real storage after the ipl record on that tape.
- */
-
-#include <linux/config.h>
-#include <asm/setup.h>
-#include <asm/lowcore.h>
-
-#define IPL_BS 1024
- .org 0
- .long 0x00080000,0x80000000+_start # The first 24 bytes are loaded
- .long 0x07000000,0x60000001 # by ipl to addresses 0-23.
- .long 0x02000000,0x20000000+IPL_BS # (a PSW and two CCWs).
- .long 0x00000000,0x00000000
- .long 0x00000000,0x00000000 # svc old psw
- .long 0x00000000,0x00000000 # program check old psw
- .long 0x00000000,0x00000000 # machine check old psw
- .long 0x00000000,0x00000000 # io old psw
- .long 0x00000000,0x00000000
- .long 0x00000000,0x00000000
- .long 0x00000000,0x00000000
- .long 0x000a0000,0x00000058 # external new psw
- .long 0x000a0000,0x00000060 # svc new psw
- .long 0x000a0000,0x00000068 # program check new psw
- .long 0x000a0000,0x00000070 # machine check new psw
- .long 0x00080000,0x80000000+.Lioint # io new psw
-
- .org 0x100
- .globl _start
-_start:
- l %r1,0xb8 # load ipl subchannel number
-#
-# find out memory size
-#
- mvc 104(8,0),.Lpcmem0 # setup program check handler
- slr %r3,%r3
- lhi %r2,1
- sll %r2,20
-.Lloop0:
- l %r0,0(%r3) # test page
- ar %r3,%r2 # add 1M
- jnm .Lloop0 # r1 < 0x80000000 -> loop
-.Lchkmem0:
- n %r3,.L4malign0 # align to multiples of 4M
- st %r3,.Lmemsize # store memory size
-.Lmemok:
-
-#
-# first write a tape mark
-#
- bras %r14,.Ltapemark
-#
-# write real storage to tape
-#
- slr %r2,%r2 # start at address 0
- bras %r14,.Lwriter # load ramdisk
-#
-# write another tape mark
-#
- bras %r14,.Ltapemark
-#
-# everything written, stop processor
-#
- lpsw .Lstopped
-#
-# subroutine for writing to tape
-# Paramters:
-# R1 = device number
-# R2 = start address
-# R3 = length
-.Lwriter:
- st %r14,.Lldret
- la %r12,.Lorbread # r12 = address of orb
- la %r5,.Lirb # r5 = address of irb
- st %r2,.Lccwwrite+4 # initialize CCW data addresses
- lctl %c6,%c6,.Lcr6
- slr %r2,%r2
-.Lldlp:
- lhi %r6,3 # 3 retries
-.Lssch:
- ssch 0(%r12) # write chunk of IPL_BS bytes
- jnz .Llderr
-.Lw4end:
- bras %r14,.Lwait4io
- tm 8(%r5),0x82 # do we have a problem ?
- jnz .Lrecov
- l %r0,.Lccwwrite+4 # update CCW data addresses
- ahi %r0,IPL_BS
- st %r0,.Lccwwrite+4
- clr %r0,%r3 # enough ?
- jl .Lldlp
-.Ldone:
- l %r14,.Lldret
- br %r14 # r2 contains the total size
-.Lrecov:
- bras %r14,.Lsense # do the sensing
- brct %r6,.Lssch # dec. retry count & branch
- j .Llderr
-.Ltapemark:
- st %r14,.Lldret
- la %r12,.Lorbmark # r12 = address of orb
- la %r5,.Lirb # r5 = address of irb
- lctl %c6,%c6,.Lcr6
- ssch 0(%r12) # write a tape mark
- jnz .Llderr
- bras %r14,.Lwait4io
- l %r14,.Lldret
- br %r14
-#
-# Sense subroutine
-#
-.Lsense:
- st %r14,.Lsnsret
- la %r7,.Lorbsense
- ssch 0(%r7) # start sense command
- jnz .Llderr
- bras %r14,.Lwait4io
- l %r14,.Lsnsret
- tm 8(%r5),0x82 # do we have a problem ?
- jnz .Llderr
- br %r14
-#
-# Wait for interrupt subroutine
-#
-.Lwait4io:
- lpsw .Lwaitpsw
-.Lioint:
- c %r1,0xb8 # compare subchannel number
- jne .Lwait4io
- tsch 0(%r5)
- slr %r0,%r0
- tm 8(%r5),0x82 # do we have a problem ?
- jnz .Lwtexit
- tm 8(%r5),0x04 # got device end ?
- jz .Lwait4io
-.Lwtexit:
- br %r14
-.Llderr:
- lpsw .Lcrash
-
- .align 8
-.Lorbread:
- .long 0x00000000,0x0080ff00,.Lccwwrite
- .align 8
-.Lorbsense:
- .long 0x00000000,0x0080ff00,.Lccwsense
- .align 8
-.Lorbmark:
- .long 0x00000000,0x0080ff00,.Lccwmark
- .align 8
-.Lccwwrite:
- .long 0x01200000+IPL_BS,0x00000000
-.Lccwsense:
- .long 0x04200001,0x00000000
-.Lccwmark:
- .long 0x1f200001,0x00000000
-.Lwaitpsw:
- .long 0x020a0000,0x80000000+.Lioint
-
-.Lirb: .long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-.Lcr6: .long 0xff000000
- .align 8
-.Lcrash:.long 0x000a0000,0x00000000
-.Lstopped: .long 0x000a0000,0x00001234
-.Lpcmem0:.long 0x00080000,0x80000000 + .Lchkmem0
-.L4malign0:.long 0xffc00000
-.Lmemsize:.long 0
-.Lldret:.long 0
-.Lsnsret: .long 0
-
- .org IPL_BS
-
#
-# Automatically generated by make menuconfig: don't edit
+# Automatically generated make config: don't edit
#
CONFIG_ARCH_S390=y
# Code maturity level options
#
CONFIG_EXPERIMENTAL=y
-CONFIG_PROCESS_DEBUG=n
+# CONFIG_PROCESS_DEBUG is not set
#
# Processor type and features
# CONFIG_BLK_DEV_NBD is not set
# CONFIG_BLK_DEV_MD is not set
CONFIG_BLK_DEV_RAM=y
-CONFIG_BLK_DEV_RAM_SIZE=8192
+CONFIG_BLK_DEV_RAM_SIZE=24576
CONFIG_BLK_DEV_INITRD=y
CONFIG_BLK_DEV_XPRAM=m
# CONFIG_MDISK is not set
CONFIG_DASD=y
+
+#
+# DASD disciplines
+#
CONFIG_DASD_ECKD=y
CONFIG_DASD_FBA=y
CONFIG_DASD_MDSK=y
# S/390 Network device support
#
CONFIG_NETDEVICES=y
+
+#
+# S390 Network devices
+#
CONFIG_CTC=y
CONFIG_IUCV=y
# CONFIG_DUMMY is not set
CONFIG_3215_CONSOLE=y
CONFIG_HWC=y
CONFIG_HWC_CONSOLE=y
+
+#
+# S/390 character device drivers
+#
+# CONFIG_S390_TAPE is not set
+
+#
+# Character devices
+#
CONFIG_UNIX98_PTYS=y
CONFIG_UNIX98_PTY_COUNT=256
# CONFIG_IP_ROUTER is not set
# CONFIG_NET_IPIP is not set
# CONFIG_NET_IPGRE is not set
+# CONFIG_IP_MROUTE is not set
# CONFIG_IP_ALIAS is not set
# CONFIG_SYN_COOKIES is not set
+
+#
+# (it is safe to leave these untouched)
+#
# CONFIG_INET_RARP is not set
CONFIG_SKB_LARGE=y
# CONFIG_IPV6 is not set
+
+#
+#
+#
# CONFIG_IPX is not set
# CONFIG_ATALK is not set
# CONFIG_X25 is not set
# CONFIG_LAPB is not set
# CONFIG_BRIDGE is not set
+# CONFIG_NET_DIVERT is not set
# CONFIG_LLC is not set
# CONFIG_ECONET is not set
# CONFIG_WAN_ROUTER is not set
#
# CONFIG_CODA_FS is not set
CONFIG_NFS_FS=y
+# CONFIG_NFS_V3 is not set
CONFIG_NFSD=y
-# CONFIG_NFSD_SUN is not set
+# CONFIG_NFSD_V3 is not set
+# CONFIG_NFSD_TCP is not set
CONFIG_SUNRPC=y
CONFIG_LOCKD=y
# CONFIG_SMB_FS is not set
#
# CONFIG_BSD_DISKLABEL is not set
# CONFIG_MAC_PARTITION is not set
+# CONFIG_MINIX_SUBPARTITION is not set
# CONFIG_SMD_DISKLABEL is not set
# CONFIG_SOLARIS_X86_PARTITION is not set
# CONFIG_UNIXWARE_DISKLABEL is not set
lh %r7,__LC_PGM_ILC # load instruction length
pgm_no_sv:
lh %r8,__LC_PGM_INT_CODE # N.B. saved int code used later KEEP it
- stosm 24(%r15),0x03 # reenable interrupts
lr %r3,%r8
la %r0,0x7f
nr %r3,%r0 # clear per-event-bit
l %r5,SP_PSW+4(15) # load psw addr
sr %r5,%r7 # substract ilc from psw
st %r5,SP_PSW+4(15) # store corrected psw addr
+pgm_per:cl %r3,BASED(.Lc20) # pseudo page fault ?
+ be BASED(pgm_go) # if yes then don't reenable interrupts
+ stosm 24(%r15),0x03 # reenable interrupts
pgm_go: basr %r14,%r9 # branch to interrupt-handler
pgm_dn: la %r0,0x80
nr %r8,%r0 # check for per exception
.Lc_ac: .long 0,0,1
.Lc_ENOSYS: .long -ENOSYS
.Lc4: .long 4
+.Lc20: .long 20
.Lc0x1202: .long 0x1202
.Lc0x1004: .long 0x1004
.Lc0x2401: .long 0x2401
.Ldispsw: .long 0x000a0000,0x00000000
.Liplccws: .long 0x02000000,0x60000018
.long 0x08000008,0x20000001
-.Liplorb: .long 0x0049504c,0x0000ff80
+.Liplorb: .long 0x0049504c,0x0040ff80
.long 0x00000000+.Liplccws
.Lschib: .long 0x00000000,0x00000000
.long 0x00000000,0x00000000
* Copyright (C) 1999, 2000 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Ingo Adlung (adlung@de.ibm.com)
+ *
+ * ChangeLog : 03/14/2001 Ingo Adlung Save not_oper func pointer prior
+ * to wipe it out
*/
#include <linux/module.h>
}
}
-
+/*
+ * Note : internal use of irqflags SA_PROBE for NOT path grouping
+ *
+ */
int s390_request_irq_special( int irq,
io_handler_func_t io_handler,
not_oper_handler_func_t not_oper_handler,
{
if ( !retval )
{
- s390_DevicePathVerification( irq, 0 );
+ if ( !(irqflags & SA_PROBE))
+ s390_DevicePathVerification( irq, 0 );
}
else
{
int new_irq;
#endif
int use_irq = irq;
+ int cpu = smp_processor_id();
//
// fix me !!!
} /* endif */
+ irq_enter(cpu, use_irq);
s390irq_spin_lock(use_irq);
#ifdef CONFIG_FAST_IRQ
#endif /* CONFIG_FAST_IRQ */
s390irq_spin_unlock(use_irq);
+ irq_exit(cpu, use_irq);
return;
}
* secondary status are presented with different interrupts.
*/
if ( ioinfo[irq]->devstat.ii.irb.scsw.stctl
- & ( SCSW_STCTL_PRIM_STATUS | SCSW_STCTL_INTER_STATUS ) )
- {
- ioinfo[irq]->devstat.rescnt = ioinfo[irq]->devstat.ii.irb.scsw.count;
+ & ( SCSW_STCTL_PRIM_STATUS | SCSW_STCTL_INTER_STATUS ) ) {
+
+ /*
+ * If the subchannel status shows status pending
+ * and we received a check condition, the count
+ * information is not meaningful.
+ */
+
+ if ( !( (dp->ii.irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)
+ && ( dp->ii.irb.scsw.cstat
+ & ( SCHN_STAT_CHN_DATA_CHK
+ | SCHN_STAT_CHN_CTRL_CHK
+ | SCHN_STAT_INTF_CTRL_CHK
+ | SCHN_STAT_PROG_CHECK
+ | SCHN_STAT_PROT_CHECK
+ | SCHN_STAT_CHAIN_CHECK )))) {
+
+ dp->rescnt = dp->ii.irb.scsw.count;
+ } else {
+ dp->rescnt = SENSE_MAX_COUNT;
+ }
+
ioinfo[irq]->devstat.cpa = ioinfo[irq]->devstat.ii.irb.scsw.cpa;
#ifdef CONFIG_DEBUG_IO
{
ret = request_irq( irq,
init_IRQ_handler,
- 0, "RDC", &devstat );
+ SA_PROBE, "RDC", &devstat );
if ( !ret )
{
pdevstat = &devstat;
ret = request_irq( irq,
init_IRQ_handler,
- 0, "RCD", pdevstat );
+ SA_PROBE, "RCD", pdevstat );
if ( !ret )
{
int irq_ret;
devstat_t devstat;
- irq_ret = request_irq( irq,
+ irq_ret = request_irq( irq,
init_IRQ_handler,
- 0,
+ SA_PROBE,
"INIT",
&devstat);
} /* endif */
#endif
- s390_DevicePathVerification( irq, 0 );
-
disable_cpu_sync_isc( irq );
ioinfo[irq]->ui.flags.syncio = 0; // global
* requests and evaluate the devstat area on return therefore
* we don't need a real I/O handler in place.
*/
- irq_ret = request_irq( irq, init_IRQ_handler, 0, "SID", &devstat);
+ irq_ret = request_irq( irq, init_IRQ_handler, SA_PROBE, "SID", &devstat);
if ( irq_ret == 0 )
inlreq = 1;
ccw1_t spid_ccw[2]; /* ccw area for SPID command */
devstat_t devstat; /* required by request_irq() */
devstat_t *pdevstat = &devstat;
+ unsigned long flags;
int irq_ret = 0; /* return code */
int retry = 5; /* retry count */
*/
irq_ret = request_irq( irq,
init_IRQ_handler,
- 0,
+ SA_PROBE,
"SPID",
pdevstat);
if ( irq_ret == 0 )
{
- s390irq_spin_lock( irq);
+ s390irq_spin_lock_irqsave( irq, flags);
spid_ccw[0].cmd_code = 0x5B; /* suspend multipath reconnect */
spid_ccw[0].cda = 0;
} /* endif */
}
-#ifdef CONFIG_DEBUG_IO
else
{
+#ifdef CONFIG_DEBUG_IO
printk( "SPID - device %04X,"
" unit check,"
" retry %d, cnt %02d,"
pdevstat->ii.sense.data[5],
pdevstat->ii.sense.data[6],
pdevstat->ii.sense.data[7]);
-
- } /* endif */
#endif
+ retry--;
+ } /* endif */
+
}
else if ( pdevstat->flag & DEVSTAT_NOT_OPER )
{
} while ( retry > 0 );
- s390irq_spin_unlock( irq);
+ s390irq_spin_unlock_irqrestore( irq, flags);
/*
* If we installed the irq action handler we have to
int irq_ret = 0; /* return code */
int retry = 5; /* retry count */
int inlreq = 0; /* inline request_irq() */
+ unsigned long flags;
if ( (irq > highest_subchannel) || (irq < 0 ) )
{
*/
irq_ret = request_irq( irq,
init_IRQ_handler,
- 0,
+ SA_PROBE,
"SNID",
pdevstat);
if ( irq_ret == 0 )
{
- s390irq_spin_lock( irq);
+ s390irq_spin_lock_irqsave( irq, flags);
snid_ccw.cmd_code = CCW_CMD_SENSE_PGID;
snid_ccw.cda = (__u32)virt_to_phys( pgid );
} while ( retry > 0 );
- s390irq_spin_unlock( irq);
+ s390irq_spin_unlock_irqrestore( irq, flags);
/*
* If we installed the irq action handler we have to
* If the device isn't known yet
* we can't lock it ...
*/
- if ( ioinfo[irq] != INVALID_STORAGE_AREA )
+ if ( ioinfo[irq] != INVALID_STORAGE_AREA )
{
s390irq_spin_lock( irq );
- lock = 1;
+ lock = 1;
dev_oper = ioinfo[irq]->ui.flags.oper;
if ( ioinfo[irq] != INVALID_STORAGE_AREA )
{
- if ( ioinfo[irq]->ui.flags.oper == 0 )
- {
- /*
- * If the device has gone
- * call not oper handler
- */
- if ( ( dev_oper == 1 )
- && ( ioinfo[irq]->nopfunc != NULL ) )
- {
+ not_oper_handler_func_t nopfunc = ioinfo[irq]->nopfunc;
- free_irq( irq,
+ if ( ioinfo[irq]->ui.flags.oper == 0 )
+ {
+ /*
+ * If the device has gone
+ * call not oper handler
+ */
+ if ( ( dev_oper == 1 )
+ && ( nopfunc != NULL ) ) {
+
+ free_irq( irq,
ioinfo[irq]->irq_desc.action->dev_id );
- ioinfo[irq]->nopfunc( irq,
- DEVSTAT_DEVICE_GONE );
+ nopfunc( irq, DEVSTAT_DEVICE_GONE );
+
} /* endif */
}
else
* ... it is and was operational, but
* the devno may have changed
*/
- else if ( ioinfo[irq]->devno != dev_no )
+ else if ( ( ioinfo[irq]->devno != dev_no )
+ && ( nopfunc != NULL ) )
{
- ioinfo[irq]->nopfunc( irq,
- DEVSTAT_REVALIDATE );
+ nopfunc( irq, DEVSTAT_REVALIDATE );
} /* endif */
break;
if (cn == '\n')
cn = ' '; /* replace newlines with space */
+ if (cn == 0x0d)
+ cn = ' '; /* replace 0x0d with space */
if (cn == ' ' && c == ' ')
continue; /* remove additional spaces */
c = cn;
/* Set up registers for signal handler */
regs->gprs[15] = (addr_t)frame;
regs->psw.addr = FIX_PSW(ka->sa.sa_handler);
+ regs->psw.mask = _USER_PSW_MASK;
}
/* Set up to return from userspace. If provided, use a stub
already in userspace. */
void smp_send_stop(void)
{
- smp_signal_others(sigp_stop_and_store_status, 0, TRUE, NULL);
+ int i;
+ u32 dummy;
+ unsigned long low_core_addr;
+
+ /* write magic number to zero page (absolute 0) */
+
+ get_cpu_lowcore(smp_processor_id()).panic_magic = __PANIC_MAGIC;
+
+ /* stop all processors */
+
+ smp_signal_others(sigp_stop, 0, TRUE, NULL);
+
+ /* store status of all processors in their lowcores (real 0) */
+
+ for (i = 0; i < smp_num_cpus; i++) {
+ if (smp_processor_id() != i) {
+ int ccode;
+ low_core_addr = (unsigned long)&get_cpu_lowcore(i);
+ do {
+ ccode = signal_processor_ps(
+ &dummy,
+ low_core_addr,
+ i,
+ sigp_store_status_at_address);
+ } while(ccode == sigp_busy);
+ }
+ }
}
/*
#include <asm/gdb-stub.h>
#endif
+#include "cpcmd.h"
+
/* Called from entry.S only */
extern void handle_per_exception(struct pt_regs *regs);
pgm_check_handler_t *pgm_check_table[128];
extern pgm_check_handler_t do_page_fault;
+extern pgm_check_handler_t do_pseudo_page_fault;
static inline void console_verbose(void)
{
return(FALSE);
}
-DO_ERROR(SIGSEGV, "Unknown program exception", default_trap_handler);
+DO_ERROR(SIGSEGV, "Unknown program exception", default_trap_handler)
DO_ERROR(SIGILL, "privileged operation", privileged_op)
DO_ERROR(SIGILL, "execute exception", execute_exception)
DO_ERROR(SIGSEGV, "addressing exception", addressing_exception)
pgm_check_table[1] = &illegal_op;
pgm_check_table[2] = &privileged_op;
pgm_check_table[3] = &execute_exception;
+ pgm_check_table[4] = &do_page_fault;
pgm_check_table[5] = &addressing_exception;
pgm_check_table[6] = &specification_exception;
pgm_check_table[7] = &data_exception;
pgm_check_table[9] = ÷_exception;
+ pgm_check_table[0x10] = &do_page_fault;
+ pgm_check_table[0x11] = &do_page_fault;
pgm_check_table[0x12] = &translation_exception;
pgm_check_table[0x13] = &special_op_exception;
+ pgm_check_table[0x14] = &do_pseudo_page_fault;
pgm_check_table[0x15] = &operand_exception;
- pgm_check_table[4] = &do_page_fault;
- pgm_check_table[0x10] = &do_page_fault;
- pgm_check_table[0x11] = &do_page_fault;
pgm_check_table[0x1C] = &privileged_op;
-
+ if (MACHINE_IS_VM)
+ cpcmd("SET PAGEX ON", NULL, 0);
}
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
+#include <linux/compatmac.h>
#include <asm/system.h>
#include <asm/uaccess.h>
goto no_context;
}
+typedef struct _pseudo_wait_t {
+ struct _pseudo_wait_t *next;
+ wait_queue_head_t queue;
+ unsigned long address;
+ int resolved;
+} pseudo_wait_t;
+
+static pseudo_wait_t *pseudo_lock_queue = NULL;
+static spinlock_t pseudo_wait_spinlock; /* spinlock to protect lock queue */
+
+/*
+ * This routine handles pseudo page faults.
+ */
+asmlinkage void
+do_pseudo_page_fault(struct pt_regs *regs, unsigned long error_code)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ pseudo_wait_t wait_struct;
+ pseudo_wait_t *ptr, *last, *next;
+ unsigned long psw_mask;
+ unsigned long address;
+ int kernel_address;
+
+ /*
+ * get psw mask of Program old psw to find out,
+ * if user or kernel mode
+ */
+ psw_mask = S390_lowcore.program_old_psw.mask;
+
+ /*
+ * get the failing address
+ * more specific the segment and page table portion of
+ * the address
+ */
+ address = S390_lowcore.trans_exc_code & 0xfffff000;
+
+ if (address & 0x80000000) {
+ /* high bit set -> a page has been swapped in by VM */
+ address &= 0x7fffffff;
+ spin_lock(&pseudo_wait_spinlock);
+ last = NULL;
+ ptr = pseudo_lock_queue;
+ while (ptr != NULL) {
+ next = ptr->next;
+ if (address == ptr->address) {
+ /*
+ * This is one of the processes waiting
+ * for the page. Unchain from the queue.
+ * There can be more than one process
+ * waiting for the same page. VM presents
+ * an initial and a completion interrupt for
+ * every process that tries to access a
+ * page swapped out by VM.
+ */
+ if (last == NULL)
+ pseudo_lock_queue = next;
+ else
+ last->next = next;
+ /* now wake up the process */
+ ptr->resolved = 1;
+ wake_up(&ptr->queue);
+ } else
+ last = ptr;
+ ptr = next;
+ }
+ spin_unlock(&pseudo_wait_spinlock);
+ } else {
+ /* Pseudo page faults in kernel mode is a bad idea */
+ if (!(psw_mask & PSW_PROBLEM_STATE)) {
+ /*
+ * VM presents pseudo page faults if the interrupted
+ * state was not disabled for interrupts. So we can
+ * get pseudo page fault interrupts while running
+ * in kernel mode. We simply access the page here
+ * while we are running disabled. VM will then swap
+ * in the page synchronously.
+ */
+ kernel_address = 0;
+ switch (S390_lowcore.trans_exc_code & 3) {
+ case 0: /* Primary Segment Table Descriptor */
+ kernel_address = 1;
+ break;
+ case 1: /* STD determined via access register */
+ if (S390_lowcore.exc_access_id == 0 ||
+ regs->acrs[S390_lowcore.exc_access_id]==0)
+ kernel_address = 1;
+ break;
+ case 2: /* Secondary Segment Table Descriptor */
+ case 3: /* Home Segment Table Descriptor */
+ break;
+ }
+ if (kernel_address)
+ /* dereference a virtual kernel address */
+ __asm__ __volatile__ (
+ " ic 0,0(%0)"
+ : : "a" (address) : "0");
+ else
+ /* dereference a virtual user address */
+ __asm__ __volatile__ (
+ " la 2,0(%0)\n"
+ " sacf 512\n"
+ " ic 2,0(2)\n"
+ " sacf 0"
+ : : "a" (address) : "2" );
+
+ return;
+ }
+ /* initialize and add element to pseudo_lock_queue */
+ init_waitqueue_head (&wait_struct.queue);
+ wait_struct.address = address;
+ wait_struct.resolved = 0;
+ spin_lock(&pseudo_wait_spinlock);
+ wait_struct.next = pseudo_lock_queue;
+ pseudo_lock_queue = &wait_struct;
+ spin_unlock(&pseudo_wait_spinlock);
+ /* go to sleep */
+ wait_event(wait_struct.queue, wait_struct.resolved);
+ }
+}
+
else
ifeq ($(CONFIG_USB),m)
MOD_SUB_DIRS += usb
+ else
+ ifdef CONFIG_INPUT_ADBHID
+ SUB_DIRS += usb
+ endif
endif
endif
* Problem: This can schedule. I moved the block device
* wakeup almost late by priority because of that.
*/
- if (DRIVER(drive))
+ if (DRIVER(drive) && DRIVER(drive)->media_change)
DRIVER(drive)->media_change(drive);
/* We kick the VFS too (see fix in ide.c revalidate) */
#ifdef CONFIG_DASD
dasd_init();
#endif
+#if defined(CONFIG_S390_TAPE) && defined(CONFIG_S390_TAPE_BLOCK)
+ tapeblock_init();
+#endif
#ifdef CONFIG_BLK_DEV_XPRAM
xpram_init();
#endif
#ifdef CONFIG_MDA_CONSOLE
extern void mda_console_init(void);
#endif
+#ifdef CONFIG_S390_TAPE_CHAR
+extern void tapechar_init(void);
+#endif
static ssize_t do_write_mem(struct file * file, void *p, unsigned long realp,
const char * buf, size_t count, loff_t *ppos)
#endif
#ifdef CONFIG_PHONE
telephony_init();
+#endif
+#if defined(CONFIG_S390_TAPE) && defined(CONFIG_S390_TAPE_CHAR)
+ tapechar_init();
#endif
return 0;
}
} else {
skb_reserve(skb, 2);
target = skb_put(skb, rlen);
- if (virt_to_bus(target + rlen) >= MAX_DMA_ADDRESS) {
+ if ((unsigned long)(target + rlen) >= MAX_DMA_ADDRESS) {
adapter->current_dma.target = target;
target = adapter->dma_buffer;
} else {
adapter->current_dma.direction = 1;
adapter->current_dma.start_time = jiffies;
- target = virt_to_bus(skb->data);
- if ((target + nlen) >= MAX_DMA_ADDRESS) {
+ if ((unsigned long)(skb->data + nlen) >= MAX_DMA_ADDRESS) {
memcpy(adapter->dma_buffer, skb->data, nlen);
target = virt_to_bus(adapter->dma_buffer);
}
+ else {
+ target = virt_to_bus(skb->data);
+ }
adapter->current_dma.skb = skb;
flags=claim_dma_lock();
fi
endmenu
+mainmenu_option next_comment
+comment 'S/390 character device drivers'
+
+tristate 'S/390 tape device support' CONFIG_S390_TAPE
+if [ "$CONFIG_S390_TAPE" != "n" ]; then
+ comment 'S/390 tape interface support'
+ bool ' Support for tape character devices' CONFIG_S390_TAPE_CHAR
+ bool ' Support for tape block devices' CONFIG_S390_TAPE_BLOCK
+ comment 'S/390 tape hardware support'
+ bool ' Support for 3490 tape hardware' CONFIG_S390_TAPE_3490
+ bool ' Support for 3480 tape hardware' CONFIG_S390_TAPE_3480
+fi
+endmenu
O_OBJS += hwc_con.o hwc_rw.o hwc_tty.o
endif
+# stuff for building tape390.o
+T390_OBJS = tape.o
+ifeq ($(CONFIG_S390_TAPE_CHAR),y)
+ T390_OBJS += tapechar.o
+endif
+ifeq ($(CONFIG_S390_TAPE_BLOCK),y)
+ T390_OBJS += tapeblock.o
+endif
+ifeq ($(CONFIG_S390_TAPE_3480),y)
+ T390_OBJS += tape3480.o
+ CONFIG_S390_TAPE_NEED_34xx = y
+endif
+ifeq ($(CONFIG_S390_TAPE_3490),y)
+ T390_OBJS += tape3490.o
+ CONFIG_S390_TAPE_NEED_34xx = y
+endif
+ifeq ($(CONFIG_S390_TAPE_NEED_34xx),y)
+ T390_OBJS += tape34xx.o
+endif
+ifeq ($(CONFIG_S390_TAPE),y)
+ O_OBJS += tape390.o
+endif
+ifeq ($(CONFIG_S390_TAPE),m)
+ M_OBJS += tape390.o
+endif
+tape390.o: $(T390_OBJS)
+ $(LD) -r -o $@ $(T390_OBJS)
include $(TOPDIR)/Rules.make
kdev_t con3215_device(struct console *c)
{
- return MKDEV(TTY_MAJOR, c->index);
+ return MKDEV(TTY_MAJOR, c->index + 64);
}
/*
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Peschke <peschke@fh-brandenburg.de>
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
*
*
*
#ifndef __HWC_H__
#define __HWC_H__
+#define HWC_EXT_INT_PARAM_ADDR 0xFFFFFFF8
+#define HWC_EXT_INT_PARAM_PEND 0x00000001
+
#define ET_OpCmd 0x01
#define ET_Msg 0x02
#define ET_StateChange 0x08
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Peschke <peschke@fh-brandenburg.de>
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
*/
#include <linux/kernel.h>
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Peschke <peschke@fh-brandenburg.de>
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
*
*
*
#include <asm/setup.h>
#include <asm/page.h>
#include <asm/s390_ext.h>
+#include <asm/irq.h>
#ifndef MIN
#define MIN(a,b) (((a<b) ? a : b))
0,
80,
1,
- 50,
+ MAX_KMEM_PAGES,
MAX_KMEM_PAGES,
0,
return condition_code;
}
-static inline unsigned char *
-ext_int_param (void)
+static inline unsigned long
+hwc_ext_int_param (void)
{
u32 param;
__asm__ __volatile__ ("L %0,128\n\t"
:"=r" (param));
- return ((unsigned char *) param);
+ return (unsigned long) param;
}
static int
internal_print (
DELAYED_WRITE,
HWC_RW_PRINT_HEADER
- "found invalid HWCB at address 0x%x. List corrupted. "
+ "found invalid HWCB at address 0x%lx. List corrupted. "
"Lost %i HWCBs with %i characters within up to %i "
"messages. Saved %i HWCB with last %i characters i"
"within up to %i messages.\n",
- (unsigned int) bad_addr,
+ (unsigned long) bad_addr,
lost_hwcb, lost_char, lost_msg,
hwc_data.hwcb_count,
ALL_HWCB_CHAR, ALL_HWCB_MTO);
}
static int
-write_event_data_2 (void)
+write_event_data_2 (u32 ext_int_param)
{
write_hwcb_t *hwcb;
int retval = 0;
#ifdef DUMP_HWC_WRITE_ERROR
- unsigned char *param;
-
- param = ext_int_param ();
- if (param != hwc_data.current_hwcb) {
+ if ((ext_int_param & HWC_EXT_INT_PARAM_ADDR)
+ != (unsigned long) hwc_data.current_hwcb) {
internal_print (
DELAYED_WRITE,
HWC_RW_PRINT_HEADER
"write_event_data_2 : "
"HWCB address does not fit "
- "(expected: 0x%x, got: 0x%x).\n",
- hwc_data.current_hwcb,
- param);
+ "(expected: 0x%lx, got: 0x%lx).\n",
+ (unsigned long) hwc_data.current_hwcb,
+ ext_int_param);
return -EINVAL;
}
#endif
}
static int
-unconditional_read_2 (void)
+unconditional_read_2 (u32 ext_int_param)
{
read_hwcb_t *hwcb = (read_hwcb_t *) hwc_data.page;
}
static int
-write_event_mask_2 (void)
+write_event_mask_2 (u32 ext_int_param)
{
init_hwcb_t *hwcb = (init_hwcb_t *) hwc_data.page;
int retval = 0;
return retval;
}
-void do_hwc_interrupt (struct pt_regs *regs, __u16 code);
+void hwc_interrupt_handler (struct pt_regs *regs, __u16 code);
int
hwc_init (unsigned long *kmem_start)
#endif
- if (register_external_interrupt (0x2401, do_hwc_interrupt) != 0)
+ if (register_external_interrupt (0x2401, hwc_interrupt_handler) != 0)
panic ("Couldn't request external interrupts 0x2401");
spin_lock_init (&hwc_data.lock);
}
void
-do_hwc_interrupt (struct pt_regs *regs, __u16 code)
+hwc_do_interrupt (u32 ext_int_param)
{
+ u32 finished_hwcb = ext_int_param & HWC_EXT_INT_PARAM_ADDR;
+ u32 evbuf_pending = ext_int_param & HWC_EXT_INT_PARAM_PEND;
- if (hwc_data.flags & HWC_INIT) {
-
- hwc_data.flags |= HWC_INTERRUPT;
- } else if (hwc_data.flags & HWC_BROKEN) {
-
- if (!do_hwc_init ()) {
- hwc_data.flags &= ~HWC_BROKEN;
- internal_print (DELAYED_WRITE,
- HWC_RW_PRINT_HEADER
- "delayed HWC setup after"
- " temporary breakdown\n");
- }
- } else {
- spin_lock (&hwc_data.lock);
-
- if (hwc_data.flags & HWC_PTIMER_RUNS) {
- del_timer (&hwc_data.poll_timer);
- hwc_data.flags &= ~HWC_PTIMER_RUNS;
- }
- if (!hwc_data.current_servc) {
-
- unconditional_read_1 ();
+ if (hwc_data.flags & HWC_PTIMER_RUNS) {
+ del_timer (&hwc_data.poll_timer);
+ hwc_data.flags &= ~HWC_PTIMER_RUNS;
+ }
+ if (finished_hwcb) {
+ if ((unsigned long) hwc_data.current_hwcb != finished_hwcb) {
+ internal_print (
+ DELAYED_WRITE,
+ HWC_RW_PRINT_HEADER
+ "interrupt: mismatch: "
+ "ext. int param. (0x%x) vs. "
+ "current HWCB (0x%x)\n",
+ ext_int_param,
+ hwc_data.current_hwcb);
} else {
switch (hwc_data.current_servc) {
case HWC_CMDW_WRITEMASK:
- write_event_mask_2 ();
+ write_event_mask_2 (ext_int_param);
break;
case HWC_CMDW_WRITEDATA:
- write_event_data_2 ();
+ write_event_data_2 (ext_int_param);
break;
case HWC_CMDW_READDATA:
- unconditional_read_2 ();
+ unconditional_read_2 (ext_int_param);
break;
+ default:
}
+ }
+ } else {
- write_event_data_1 ();
+ if (hwc_data.current_hwcb) {
+ internal_print (
+ DELAYED_WRITE,
+ HWC_RW_PRINT_HEADER
+ "interrupt: mismatch: "
+ "ext. int. param. (0x%x) vs. "
+ "current HWCB (0x%x)\n",
+ ext_int_param,
+ hwc_data.current_hwcb);
}
- if (hwc_data.calls != NULL)
- if (hwc_data.calls->wake_up != NULL)
- (hwc_data.calls->wake_up) ();
+ }
+
+ if (evbuf_pending) {
+
+ unconditional_read_1 ();
+ } else {
+
+ write_event_data_1 ();
+ }
+
+ if (!hwc_data.calls || !hwc_data.calls->wake_up)
+ return;
+ (hwc_data.calls->wake_up) ();
+}
+
+void
+hwc_interrupt_handler (struct pt_regs *regs, __u16 code)
+{
+ u32 ext_int_param = hwc_ext_int_param ();
+ int cpu = smp_processor_id ();
+
+ irq_enter (cpu, 0x2401);
+
+ if (hwc_data.flags & HWC_INIT) {
+
+ hwc_data.flags |= HWC_INTERRUPT;
+ } else if (hwc_data.flags & HWC_BROKEN) {
+
+ if (!do_hwc_init ()) {
+ hwc_data.flags &= ~HWC_BROKEN;
+ internal_print (DELAYED_WRITE,
+ HWC_RW_PRINT_HEADER
+ "delayed HWC setup after"
+ " temporary breakdown"
+ " (ext. int. parameter=0x%x)\n",
+ ext_int_param);
+ }
+ } else {
+ spin_lock (&hwc_data.lock);
+ hwc_do_interrupt (ext_int_param);
spin_unlock (&hwc_data.lock);
}
+ irq_exit (cpu, 0x2401);
}
void
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Peschke <peschke@fh-brandenburg.de>
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
*/
#ifndef __HWC_RW_H__
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Peschke <peschke@fh-brandenburg.de>
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
*
* Thanks to Martin Schwidefsky.
*/
switch (cmd) {
case TIOCHWCTTYSINTRC:
- count = strnlen_user((const char *)arg, HWC_TTY_MAX_CNTL_SIZE);
- if (!count)
- return -EFAULT;
+ count = strlen_user ((const char *) arg);
if (count > HWC_TTY_MAX_CNTL_SIZE)
return -EINVAL;
strncpy_from_user (hwc_tty_data.ioctl.intr_char,
*
* S390 version
* Copyright (C) 2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Author(s): Martin Peschke <peschke@fh-brandenburg.de>
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
*/
#ifndef __HWC_TTY_H__
--- /dev/null
+
+/***********************************************************************
+ * drivers/s390/char/tape.c
+ * tape device driver for S/390 and zSeries tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ***********************************************************************
+ */
+
+#include "tapedefs.h"
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <asm/types.h>
+#include <asm/ccwcache.h>
+#include <asm/idals.h>
+#include <asm/ebcdic.h>
+#include <linux/compatmac.h>
+#ifdef MODULE
+#include <linux/module.h>
+#endif
+#include <asm/debug.h>
+#ifdef CONFIG_S390_TAPE_DYNAMIC
+#include <asm/s390dyn.h>
+#endif
+#include "tape.h"
+#ifdef CONFIG_S390_TAPE_3490
+#include "tape3490.h"
+#endif
+#ifdef CONFIG_S390_TAPE_3480
+#include "tape3480.h"
+#endif
+#ifdef CONFIG_S390_TAPE_BLOCK
+#include "tapeblock.h"
+#endif
+#ifdef CONFIG_S390_TAPE_CHAR
+#include "tapechar.h"
+#endif
+#ifdef CONFIG_PROC_FS
+#include <linux/vmalloc.h>
+#endif
+#define PRINTK_HEADER "T390:"
+
+
+/* state handling routines */
+inline void tapestate_set (tape_info_t * ti, int newstate);
+inline int tapestate_get (tape_info_t * ti);
+void tapestate_event (tape_info_t * ti, int event);
+
+/* our globals */
+tape_info_t *first_tape_info = NULL;
+tape_discipline_t *first_discipline = NULL;
+tape_frontend_t *first_frontend = NULL;
+devreg_t* tape_devreg[128];
+int devregct=0;
+
+#ifdef TAPE_DEBUG
+debug_info_t *tape_debug_area = NULL;
+#endif
+
+char* state_verbose[TS_SIZE]={
+ "TS_UNUSED", "TS_IDLE", "TS_DONE", "TS_FAILED",
+ "TS_BLOCK_INIT",
+ "TS_BSB_INIT",
+ "TS_BSF_INIT",
+ "TS_DSE_INIT",
+ "TS_EGA_INIT",
+ "TS_FSB_INIT",
+ "TS_FSF_INIT",
+ "TS_LDI_INIT",
+ "TS_LBL_INIT",
+ "TS_MSE_INIT",
+ "TS_NOP_INIT",
+ "TS_RBA_INIT",
+ "TS_RBI_INIT",
+ "TS_RBU_INIT",
+ "TS_RBL_INIT",
+ "TS_RDC_INIT",
+ "TS_RFO_INIT",
+ "TS_RSD_INIT",
+ "TS_REW_INIT",
+ "TS_REW_RELEASE_INIT",
+ "TS_RUN_INIT",
+ "TS_SEN_INIT",
+ "TS_SID_INIT",
+ "TS_SNP_INIT",
+ "TS_SPG_INIT",
+ "TS_SWI_INIT",
+ "TS_SMR_INIT",
+ "TS_SYN_INIT",
+ "TS_TIO_INIT",
+ "TS_UNA_INIT",
+ "TS_WRI_INIT",
+ "TS_WTM_INIT",
+ "TS_NOT_OPER"};
+
+char* event_verbose[TE_SIZE]= {
+ "TE_START", "TE_DONE", "TE_FAILED", "TE_ERROR", "TE_OTHER"};
+
+/* our root devfs handle */
+#ifdef CONFIG_DEVFS_FS
+devfs_handle_t tape_devfs_root_entry;
+
+inline void
+tape_mkdevfsroots (tape_info_t* ti)
+{
+ char devno [5];
+ sprintf (devno,"%04X",ti->devinfo.devno);
+ ti->devfs_dir=devfs_mk_dir (tape_devfs_root_entry, devno, ti);
+}
+
+inline void
+tape_rmdevfsroots (tape_info_t* ti)
+{
+ devfs_unregister (ti->devfs_dir);
+}
+#endif
+
+#ifdef CONFIG_PROC_FS
+/* our proc tapedevices entry */
+static struct proc_dir_entry *tape_devices_entry;
+
+typedef struct {
+ char *data;
+ int len;
+} tempinfo_t;
+
+
+static int
+tape_devices_open (struct inode *inode, struct file *file)
+{
+ int size=80;
+ tape_info_t* ti;
+ tempinfo_t* tempinfo;
+ char* data;
+ int pos=0;
+ tempinfo = kmalloc (sizeof(tempinfo_t),GFP_KERNEL);
+ if (!tempinfo)
+ return -ENOMEM;
+ for (ti=first_tape_info;ti!=NULL;ti=ti->next)
+ size+=80; // FIXME: Guess better!
+ data=vmalloc(size);
+ if (!data) {
+ kfree (tempinfo);
+ return -ENOMEM;
+ }
+ pos+=sprintf(data+pos,"TapeNo\tDevNo\tCuType\tCuModel\tDevType\tDevModel\tState\n");
+ for (ti=first_tape_info;ti!=NULL;ti=ti->next) {
+ pos+=sprintf(data+pos,"%d\t%04X\t%04X\t%02X\t%04X\t%02X\t\t%s\n",ti->rew_minor/2,
+ ti->devinfo.devno,ti->devinfo.sid_data.cu_type,
+ ti->devinfo.sid_data.cu_model,ti->devinfo.sid_data.dev_type,
+ ti->devinfo.sid_data.dev_model,((tapestate_get(ti) >= 0) &&
+ (tapestate_get(ti) < TS_SIZE)) ?
+ state_verbose[tapestate_get (ti)] : "TS UNKNOWN");
+ }
+ tempinfo->len=pos;
+ tempinfo->data=data;
+ file->private_data= (void*) tempinfo;
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif
+ return 0;
+}
+
+static ssize_t
+tape_devices_read (struct file *file, char *user_buf, size_t user_len, loff_t * offset)
+{
+ loff_t len;
+ tempinfo_t *p_info = (tempinfo_t *) file->private_data;
+
+ if (*offset >= p_info->len) {
+ return 0; /* EOF */
+ } else {
+ len = user_len<(p_info->len - *offset)?user_len:(p_info->len - *offset);
+ if (copy_to_user (user_buf, &(p_info->data[*offset]), len))
+ return -EFAULT;
+ (*offset) += len;
+ return len; /* number of bytes "read" */
+ }
+}
+
+static int
+tape_devices_release (struct inode *inode, struct file *file)
+{
+ int rc = 0;
+ tempinfo_t *p_info = (tempinfo_t *) file->private_data;
+ if (p_info) {
+ if (p_info->data)
+ vfree (p_info->data);
+ kfree (p_info);
+ }
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif
+ return rc;
+}
+
+static struct file_operations tape_devices_file_ops =
+{
+ read:tape_devices_read, /* read */
+ open:tape_devices_open, /* open */
+ release:tape_devices_release, /* close */
+};
+
+static struct inode_operations tape_devices_inode_ops =
+{
+#if !(LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+ default_file_ops:&tape_devices_file_ops /* file ops */
+#endif /* LINUX_IS_24 */
+};
+#endif /* CONFIG_PROC_FS */
+
+/* SECTION: Parameters for tape */
+char *tape[256] = { NULL, };
+
+#ifndef MODULE
+static char tape_parm_string[1024] __initdata = { 0, };
+static void
+tape_split_parm_string (char *str)
+{
+ char *tmp = str;
+ int count = 0;
+ while (tmp != NULL && *tmp != '\0') {
+ char *end;
+ int len;
+ end = strchr (tmp, ',');
+ if (end == NULL) {
+ len = strlen (tmp) + 1;
+ } else {
+ len = (long) end - (long) tmp + 1;
+ *end = '\0';
+ end++;
+ }
+ tape[count] = kmalloc (len * sizeof (char), GFP_ATOMIC);
+ if (tape[count] == NULL) {
+ printk (KERN_WARNING PRINTK_HEADER
+ "can't store tape= parameter no %d\n",
+ count + 1);
+ break;
+ }
+ memset (tape[count], 0, len * sizeof (char));
+ memcpy (tape[count], tmp, len * sizeof (char));
+ count++;
+ tmp = end;
+ };
+}
+
+void __init
+tape_parm_setup (char *str, int *ints)
+{
+ int len = strlen (tape_parm_string);
+ if (len != 0) {
+ strcat (tape_parm_string, ",");
+ }
+ strcat (tape_parm_string, str);
+}
+
+int __init
+tape_parm_call_setup (char *str)
+{
+ int dummy;
+ tape_parm_setup (str, &dummy);
+ return 1;
+}
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,2,16))
+__setup("tape=", tape_parm_call_setup);
+#endif /* kernel <2.2.19 */
+#endif /* not defined MODULE */
+
+static inline int
+tape_parm_strtoul (char *str, char **stra)
+{
+ char *temp = str;
+ int val;
+ if (*temp == '0') {
+ temp++; /* strip leading zero */
+ if (*temp == 'x')
+ temp++; /* strip leading x */
+ }
+ val = simple_strtoul (temp, &temp, 16); /* interpret anything as hex */
+ *stra = temp;
+ return val;
+}
+
+static inline devreg_t *
+tape_create_devreg (int devno)
+{
+ devreg_t *devreg = kmalloc (sizeof (devreg_t), GFP_KERNEL);
+ if (devreg != NULL) {
+ memset (devreg, 0, sizeof (devreg_t));
+ devreg->ci.devno = devno;
+ devreg->flag = DEVREG_TYPE_DEVNO;
+ devreg->oper_func = tape_oper_handler;
+ }
+ return devreg;
+}
+
+static inline void
+tape_parm_parse (char **str)
+{
+ char *temp;
+ int from, to,i,irq=0,rc,retries=0,tape_num=0;
+ s390_dev_info_t dinfo;
+ tape_info_t* ti,*tempti;
+ tape_discipline_t* disc;
+ long lockflags;
+ if (*str==NULL) {
+ /* no params present -> leave */
+ return;
+ }
+ while (*str) {
+ temp = *str;
+ from = 0;
+ to = 0;
+
+ /* turn off autodetect mode, if any range is present */
+ from = tape_parm_strtoul (temp, &temp);
+ to = from;
+ if (*temp == '-') {
+ temp++;
+ to = tape_parm_strtoul (temp, &temp);
+ }
+ for (i=from;i<=to;i++) {
+ retries=0;
+ // register for attch/detach of a devno
+ tape_devreg[devregct]=tape_create_devreg(i);
+ if (tape_devreg[devregct]==NULL) {
+ PRINT_WARN ("Could not create devreg for devno %04x, dyn. attach for this devno deactivated.\n",i);
+ } else {
+ s390_device_register (tape_devreg[devregct++]);
+ }
+ // we are activating a device if it is present
+ for (irq = get_irq_first(); irq!=-ENODEV; irq=get_irq_next(irq)) {
+ rc = get_dev_info_by_irq (irq, &dinfo);
+
+ disc = first_discipline;
+ while ((dinfo.devno == i) && (disc != NULL) && (disc->cu_type != dinfo.sid_data.cu_type))
+ disc = (tape_discipline_t *) (disc->next);
+ if ((disc == NULL) || (rc == -ENODEV) || (i!=dinfo.devno)) {
+ continue;
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"det irq: ");
+ debug_int_event (tape_debug_area,3,irq);
+ debug_text_event (tape_debug_area,3,"cu: ");
+ debug_int_event (tape_debug_area,3,disc->cu_type);
+#endif /* TAPE_DEBUG */
+ PRINT_INFO ("using devno %04x with discipline %04x on irq %d as tape device %d\n",dinfo.devno,dinfo.sid_data.cu_type,irq,tape_num/2);
+ /* Allocate tape structure */
+ ti = kmalloc (sizeof (tape_info_t), GFP_ATOMIC);
+ if (ti == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,3,"ti:no mem ");
+#endif /* TAPE_DEBUG */
+ PRINT_INFO ("tape: can't allocate memory for "
+ "tape info structure\n");
+ continue;
+ }
+ memset(ti,0,sizeof(tape_info_t));
+ ti->discipline = disc;
+ disc->tape = ti;
+ rc = tape_setup (ti, irq, tape_num);
+ if (rc) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"tsetup err");
+ debug_int_exception (tape_debug_area,3,rc);
+#endif /* TAPE_DEBUG */
+ kfree (ti);
+ } else {
+ s390irq_spin_lock_irqsave (irq, lockflags);
+ if (first_tape_info == NULL) {
+ first_tape_info = ti;
+ } else {
+ tempti = first_tape_info;
+ while (tempti->next != NULL)
+ tempti = tempti->next;
+ tempti->next = ti;
+ }
+ s390irq_spin_unlock_irqrestore (irq, lockflags);
+ }
+ }
+ tape_num+=2;
+ }
+ str++;
+ }
+}
+
+
+/* SECTION: Managing wrappers for ccwcache */
+
+#define TAPE_EMERGENCY_REQUESTS 16
+
+static ccw_req_t *tape_emergency_req[TAPE_EMERGENCY_REQUESTS] =
+{NULL,};
+static spinlock_t tape_emergency_req_lock = SPIN_LOCK_UNLOCKED;
+
+static void
+tape_init_emergency_req (void)
+{
+ int i;
+ for (i = 0; i < TAPE_EMERGENCY_REQUESTS; i++) {
+ tape_emergency_req[i] = (ccw_req_t *) get_free_page (GFP_KERNEL);
+ }
+}
+
+#ifdef MODULE // We only cleanup the emergency requests on module unload.
+static void
+tape_cleanup_emergency_req (void)
+{
+ int i;
+ for (i = 0; i < TAPE_EMERGENCY_REQUESTS; i++) {
+ if (tape_emergency_req[i])
+ free_page ((long) (tape_emergency_req[i]));
+ else
+ printk (KERN_WARNING PRINTK_HEADER "losing one page for 'in-use' emergency request\n");
+ }
+}
+#endif
+
+ccw_req_t *
+tape_alloc_request (char *magic, int cplength, int datasize)
+{
+ ccw_req_t *rv = NULL;
+ int i;
+ if ((rv = ccw_alloc_request (magic, cplength, datasize)) != NULL) {
+ return rv;
+ }
+ if (cplength * sizeof (ccw1_t) + datasize + sizeof (ccw_req_t) > PAGE_SIZE) {
+ return NULL;
+ }
+ spin_lock (&tape_emergency_req_lock);
+ for (i = 0; i < TAPE_EMERGENCY_REQUESTS; i++) {
+ if (tape_emergency_req[i] != NULL) {
+ rv = tape_emergency_req[i];
+ tape_emergency_req[i] = NULL;
+ }
+ }
+ spin_unlock (&tape_emergency_req_lock);
+ if (rv) {
+ memset (rv, 0, PAGE_SIZE);
+ rv->cache = (kmem_cache_t *) (tape_emergency_req + i);
+ strncpy ((char *) (&rv->magic), magic, 4);
+ ASCEBC ((char *) (&rv->magic), 4);
+ rv->cplength = cplength;
+ rv->datasize = datasize;
+ rv->data = (void *) ((long) rv + PAGE_SIZE - datasize);
+ rv->cpaddr = (ccw1_t *) ((long) rv + sizeof (ccw_req_t));
+ }
+ return rv;
+}
+
+void
+tape_free_request (ccw_req_t * request)
+{
+ if (request->cache >= (kmem_cache_t *) tape_emergency_req &&
+ request->cache <= (kmem_cache_t *) (tape_emergency_req + TAPE_EMERGENCY_REQUESTS)) {
+ *((ccw_req_t **) (request->cache)) = request;
+ } else {
+ clear_normalized_cda ((ccw1_t *) (request->cpaddr)); // avoid memory leak caused by modeset_byte
+ ccw_free_request (request);
+ }
+}
+
+/*
+ * Allocate a ccw request and reserve it for tape driver
+ */
+inline
+ ccw_req_t *
+tape_alloc_ccw_req (tape_info_t * ti, int cplength, int datasize)
+{
+ char tape_magic_id[] = "tape";
+ ccw_req_t *cqr = NULL;
+
+ if (!ti)
+ return NULL;
+ cqr = tape_alloc_request (tape_magic_id, cplength, datasize);
+
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ PRINT_WARN ("empty CQR generated\n");
+#endif
+ }
+ cqr->magic = TAPE_MAGIC; /* sets an identifier for tape driver */
+ cqr->device = ti; /* save pointer to tape info */
+ return cqr;
+}
+
+/*
+ * Find the tape_info_t structure associated with irq
+ */
+static inline tape_info_t *
+tapedev_find_info (int irq)
+{
+ tape_info_t *ti;
+
+ ti = first_tape_info;
+ if (ti != NULL)
+ do {
+ if (ti->devinfo.irq == irq)
+ break;
+ } while ((ti = (tape_info_t *) ti->next) != NULL);
+ return ti;
+}
+
+#define QUEUE_THRESHOLD 5
+
+/*
+ * Tape interrupt routine, called from Ingo's I/O layer
+ */
+void
+tape_irq (int irq, void *int_parm, struct pt_regs *regs)
+{
+ tape_info_t *ti = tapedev_find_info (irq);
+
+ /* analyse devstat and fire event */
+ if (ti->devstat.dstat & DEV_STAT_UNIT_CHECK) {
+ tapestate_event (ti, TE_ERROR);
+ } else if (ti->devstat.dstat & (DEV_STAT_DEV_END)) {
+ tapestate_event (ti, TE_DONE);
+ } else
+ tapestate_event (ti, TE_OTHER);
+}
+
+int
+tape_oper_handler ( int irq, struct _devreg *dreg) {
+ tape_info_t* ti=first_tape_info;
+ tape_info_t* newtape;
+ int rc,tape_num,retries=0,i;
+ s390_dev_info_t dinfo;
+ tape_discipline_t* disc;
+#ifdef CONFIG_DEVFS_FS
+ tape_frontend_t* frontend;
+#endif
+ long lockflags;
+ while ((ti!=NULL) && (ti->devinfo.irq!=irq))
+ ti=ti->next;
+ if (ti!=NULL) {
+ // irq is (still) used by tape. tell ingo to try again later
+ PRINT_WARN ("Oper handler for irq %d called while irq still (internaly?) used.\n",irq);
+ return -EAGAIN;
+ }
+ // irq is not used by tape
+ rc = get_dev_info_by_irq (irq, &dinfo);
+ if (rc == -ENODEV) {
+ retries++;
+ rc = get_dev_info_by_irq (irq, &dinfo);
+ if (retries > 5) {
+ PRINT_WARN ("No device information for new dev. could be retrieved.\n");
+ return -ENODEV;
+ }
+ }
+ disc = first_discipline;
+ while ((disc != NULL) && (disc->cu_type != dinfo.sid_data.cu_type))
+ disc = (tape_discipline_t *) (disc->next);
+ if (disc == NULL)
+ PRINT_WARN ("No matching discipline for cu_type %x found, ignoring device %04x.\n",dinfo.sid_data.cu_type,dinfo.devno);
+ if (rc == -ENODEV)
+ PRINT_WARN ("No device information for new dev. could be retrieved.\n");
+ if ((disc == NULL) || (rc == -ENODEV))
+ return -ENODEV;
+
+ /* Allocate tape structure */
+ ti = kmalloc (sizeof (tape_info_t), GFP_ATOMIC);
+ if (ti == NULL) {
+ PRINT_INFO ( "tape: can't allocate memory for "
+ "tape info structure\n");
+ return -ENOBUFS;
+ }
+ memset(ti,0,sizeof(tape_info_t));
+ ti->discipline = disc;
+ disc->tape = ti;
+ tape_num=0;
+ if (*tape) {
+ // we have static device ranges, so fingure out the tape_num of the attached tape
+ for (i=0;i<devregct;i++)
+ if (tape_devreg[i]->ci.devno==dinfo.devno) {
+ tape_num=2*i;
+ break;
+ }
+ } else {
+ // we are running in autoprobe mode, find a free tape_num
+ newtape=first_tape_info;
+ while (newtape!=NULL) {
+ if (newtape->rew_minor==tape_num) {
+ // tape num in use. try next one
+ tape_num+=2;
+ newtape=first_tape_info;
+ } else {
+ // tape num not used by newtape. look at next tape info
+ newtape=newtape->next;
+ }
+ }
+ }
+ rc = tape_setup (ti, irq, tape_num);
+ if (rc) {
+ kfree (ti);
+ return -ENOBUFS;
+ }
+#ifdef CONFIG_DEVFS_FS
+ for (frontend=first_frontend;frontend!=NULL;frontend=frontend->next)
+ frontend->mkdevfstree(ti);
+#endif
+ s390irq_spin_lock_irqsave (irq,lockflags);
+ if (first_tape_info == NULL) {
+ first_tape_info = ti;
+ } else {
+ newtape = first_tape_info;
+ while (newtape->next != NULL)
+ newtape = newtape->next;
+ newtape->next = ti;
+ }
+ s390irq_spin_unlock_irqrestore (irq, lockflags);
+ return 0;
+}
+
+
+static void
+tape_noper_handler ( int irq, int status ) {
+ tape_info_t *ti=first_tape_info;
+ tape_info_t *lastti;
+#ifdef CONFIG_DEVFS_FS
+ tape_frontend_t *frontend;
+#endif
+ long lockflags;
+ s390irq_spin_lock_irqsave(irq,lockflags);
+ while (ti!=NULL && ti->devinfo.irq!=irq) ti=ti->next;
+ if (ti==NULL) return;
+ if (tapestate_get(ti)!=TS_UNUSED) {
+ // device is in use!
+ PRINT_WARN ("Tape #%d was detached while it was busy. Expect errors!",ti->blk_minor/2);
+ tapestate_set(ti,TS_NOT_OPER);
+ ti->rc=-ENODEV;
+ ti->wanna_wakeup=1;
+ switch (tapestate_get(ti)) {
+ case TS_REW_RELEASE_INIT:
+ tapestate_set(ti,TS_NOT_OPER);
+ wake_up (&ti->wq);
+ break;
+#ifdef CONFIG_S390_TAPE_BLOCK
+ case TS_BLOCK_INIT:
+ tapestate_set(ti,TS_NOT_OPER);
+ schedule_tapeblock_exec_IO(ti);
+ break;
+#endif
+ default:
+ tapestate_set(ti,TS_NOT_OPER);
+ wake_up_interruptible (&ti->wq);
+ }
+ } else {
+ // device is unused!
+ PRINT_WARN ("Tape #%d was detached.\n",ti->blk_minor/2);
+ if (ti==first_tape_info) {
+ first_tape_info=ti->next;
+ } else {
+ lastti=first_tape_info;
+ while (lastti->next!=ti) lastti=lastti->next;
+ lastti->next=ti->next;
+ }
+#ifdef CONFIG_DEVFS_FS
+ for (frontend=first_frontend;frontend!=NULL;frontend=frontend->next)
+ frontend->rmdevfstree(ti);
+ tape_rmdevfsroots(ti);
+#endif
+ kfree(ti);
+ }
+ s390irq_spin_unlock_irqrestore(irq,lockflags);
+ return;
+}
+
+
+void
+tape_dump_sense (devstat_t * stat)
+{
+#ifdef TAPE_DEBUG
+ int sl;
+#endif
+#if 0
+
+ PRINT_WARN ("------------I/O resulted in unit check:-----------\n");
+ for (sl = 0; sl < 4; sl++) {
+ PRINT_WARN ("Sense:");
+ for (sct = 0; sct < 8; sct++) {
+ PRINT_WARN (" %2d:0x%02X", 8 * sl + sct,
+ stat->ii.sense.data[8 * sl + sct]);
+ }
+ PRINT_WARN ("\n");
+ }
+ PRINT_INFO ("Sense data: %02X%02X%02X%02X %02X%02X%02X%02X "
+ " %02X%02X%02X%02X %02X%02X%02X%02X \n",
+ stat->ii.sense.data[0], stat->ii.sense.data[1],
+ stat->ii.sense.data[2], stat->ii.sense.data[3],
+ stat->ii.sense.data[4], stat->ii.sense.data[5],
+ stat->ii.sense.data[6], stat->ii.sense.data[7],
+ stat->ii.sense.data[8], stat->ii.sense.data[9],
+ stat->ii.sense.data[10], stat->ii.sense.data[11],
+ stat->ii.sense.data[12], stat->ii.sense.data[13],
+ stat->ii.sense.data[14], stat->ii.sense.data[15]);
+ PRINT_INFO ("Sense data: %02X%02X%02X%02X %02X%02X%02X%02X "
+ " %02X%02X%02X%02X %02X%02X%02X%02X \n",
+ stat->ii.sense.data[16], stat->ii.sense.data[17],
+ stat->ii.sense.data[18], stat->ii.sense.data[19],
+ stat->ii.sense.data[20], stat->ii.sense.data[21],
+ stat->ii.sense.data[22], stat->ii.sense.data[23],
+ stat->ii.sense.data[24], stat->ii.sense.data[25],
+ stat->ii.sense.data[26], stat->ii.sense.data[27],
+ stat->ii.sense.data[28], stat->ii.sense.data[29],
+ stat->ii.sense.data[30], stat->ii.sense.data[31]);
+#endif
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"SENSE:");
+ for (sl=0;sl<31;sl++) {
+ debug_int_event (tape_debug_area,3,stat->ii.sense.data[sl]);
+ }
+ debug_int_exception (tape_debug_area,3,stat->ii.sense.data[31]);
+#endif
+}
+
+/*
+ * Setup tape_info_t structure of a tape device
+ */
+int
+tape_setup (tape_info_t * ti, int irq, int minor)
+{
+ long lockflags;
+ int rc = 0;
+
+ if (minor>254) {
+ PRINT_WARN ("Device id %d on irq %d will not be accessible since this driver is restricted to 128 devices.\n",minor/2,irq);
+ return -EINVAL;
+ }
+ rc = get_dev_info_by_irq (irq, &(ti->devinfo));
+ if (rc == -ENODEV) { /* end of device list */
+ return rc;
+ }
+ ti->rew_minor = minor;
+ ti->nor_minor = minor + 1;
+ ti->blk_minor = minor;
+#ifdef CONFIG_DEVFS_FS
+ tape_mkdevfsroots(ti);
+#endif
+ /* Register IRQ */
+#ifdef CONFIG_S390_TAPE_DYNAMIC
+ rc = s390_request_irq_special (irq, tape_irq, tape_noper_handler,0, "tape", &(ti->devstat));
+#else
+ rc = s390_request_irq (irq, tape_irq, 0, "tape", &(ti->devstat));
+#endif
+ s390irq_spin_lock_irqsave (irq, lockflags);
+ ti->next = NULL;
+ if (rc)
+ PRINT_WARN ("Cannot register irq %d, rc=%d\n", irq, rc);
+ init_waitqueue_head (&ti->wq);
+ ti->kernbuf = ti->userbuf = ti->discdata = NULL;
+ tapestate_set (ti, TS_UNUSED);
+ ti->discdata=NULL;
+ ti->discipline->setup_assist (ti);
+ ti->wanna_wakeup=0;
+ s390irq_spin_unlock_irqrestore (irq, lockflags);
+ return rc;
+}
+
+/*
+ * tape_init will register the driver for each tape.
+ */
+int
+tape_init (void)
+{
+ long lockflags;
+ s390_dev_info_t dinfo;
+ tape_discipline_t *disc;
+ tape_info_t *ti = NULL, *tempti = NULL;
+ char *opt_char,*opt_block,*opt_3490,*opt_3480;
+ int irq = 0, rc, retries = 0, tape_num = 0;
+ static int initialized=0;
+
+ if (initialized) // Only init the devices once
+ return 0;
+ initialized=1;
+
+#ifdef TAPE_DEBUG
+ tape_debug_area = debug_register ( "tape", 3, 2, 10);
+ debug_register_view(tape_debug_area,&debug_hex_ascii_view);
+ debug_text_event (tape_debug_area,3,"begin init");
+#endif /* TAPE_DEBUG */
+
+ /* print banner */
+ PRINT_WARN ("IBM S/390 Tape Device Driver (v1.01).\n");
+ PRINT_WARN ("(C) IBM Deutschland Entwicklung GmbH, 2000\n");
+ opt_char=opt_block=opt_3480=opt_3490="not present";
+#ifdef CONFIG_S390_TAPE_CHAR
+ opt_char="built in";
+#endif
+#ifdef CONFIG_S390_TAPE_BLOCK
+ opt_block="built in";
+#endif
+#ifdef CONFIG_S390_TAPE_3480
+ opt_3480="built in";
+#endif
+#ifdef CONFIG_S390_TAPE_3490
+ opt_3490="built in";
+#endif
+ /* print feature info */
+ PRINT_WARN ("character device frontend : %s\n",opt_char);
+ PRINT_WARN ("block device frontend : %s\n",opt_block);
+ PRINT_WARN ("support for 3480 compatible : %s\n",opt_3480);
+ PRINT_WARN ("support for 3490 compatible : %s\n",opt_3490);
+
+#ifndef MODULE
+ tape_split_parm_string(tape_parm_string);
+#endif
+ if (*tape)
+ PRINT_INFO ("Using ranges supplied in parameters, disabling autoprobe mode.\n");
+ else
+ PRINT_INFO ("No parameters supplied, enabling autoprobe mode for all supported devices.\n");
+#ifdef CONFIG_S390_TAPE_3490
+ if (*tape)
+ first_discipline = tape3490_init (0); // no autoprobe for devices
+ else
+ first_discipline = tape3490_init (1); // do autoprobe since no parm specified
+ first_discipline->next = NULL;
+#endif
+
+#ifdef CONFIG_S390_TAPE_3480
+ if (first_discipline == NULL) {
+ if (*tape)
+ first_discipline = tape3480_init (0); // no autoprobe for devices
+ else
+ first_discipline = tape3480_init (1); // do autoprobe since no parm specified
+ first_discipline->next = NULL;
+ } else {
+ if (*tape)
+ first_discipline->next = tape3480_init (0); // no autoprobe for devices
+ else
+ first_discipline->next = tape3480_init (1); // do autoprobe since no parm specified
+ ((tape_discipline_t*) (first_discipline->next))->next=NULL;
+ }
+#endif
+#ifdef CONFIG_DEVFS_FS
+ tape_devfs_root_entry=devfs_mk_dir (NULL, "tape", NULL);
+#endif CONFIG_DEVFS_FS
+
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"dev detect");
+#endif /* TAPE_DEBUG */
+ /* Allocate the tape structures */
+ if (*tape!=NULL) {
+ // we have parameters, continue with parsing the parameters and set the devices online
+ tape_parm_parse (tape);
+ } else {
+ // we are running in autodetect mode, search all devices for compatibles
+ for (irq = get_irq_first(); irq!=-ENODEV; irq=get_irq_next(irq)) {
+ rc = get_dev_info_by_irq (irq, &dinfo);
+ disc = first_discipline;
+ while ((disc != NULL) && (disc->cu_type != dinfo.sid_data.cu_type))
+ disc = (tape_discipline_t *) (disc->next);
+ if ((disc == NULL) || (rc == -ENODEV))
+ continue;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"det irq: ");
+ debug_int_event (tape_debug_area,3,irq);
+ debug_text_event (tape_debug_area,3,"cu: ");
+ debug_int_event (tape_debug_area,3,disc->cu_type);
+#endif /* TAPE_DEBUG */
+ PRINT_INFO ("using devno %04x with discipline %04x on irq %d as tape device %d\n",dinfo.devno,dinfo.sid_data.cu_type,irq,tape_num/2);
+ /* Allocate tape structure */
+ ti = kmalloc (sizeof (tape_info_t), GFP_ATOMIC);
+ if (ti == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,3,"ti:no mem ");
+#endif /* TAPE_DEBUG */
+ PRINT_INFO ("tape: can't allocate memory for "
+ "tape info structure\n");
+ continue;
+ }
+ memset(ti,0,sizeof(tape_info_t));
+ ti->discipline = disc;
+ disc->tape = ti;
+ rc = tape_setup (ti, irq, tape_num);
+ if (rc) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"tsetup err");
+ debug_int_exception (tape_debug_area,3,rc);
+#endif /* TAPE_DEBUG */
+ kfree (ti);
+ } else {
+ s390irq_spin_lock_irqsave (irq, lockflags);
+ if (first_tape_info == NULL) {
+ first_tape_info = ti;
+ } else {
+ tempti = first_tape_info;
+ while (tempti->next != NULL)
+ tempti = tempti->next;
+ tempti->next = ti;
+ }
+ tape_num += 2;
+ s390irq_spin_unlock_irqrestore (irq, lockflags);
+ }
+ }
+ }
+
+ /* Allocate local buffer for the ccwcache */
+ tape_init_emergency_req ();
+#ifdef CONFIG_PROC_FS
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+ tape_devices_entry = create_proc_entry ("tapedevices",
+ S_IFREG | S_IRUGO | S_IWUSR,
+ &proc_root);
+ tape_devices_entry->proc_fops = &tape_devices_file_ops;
+ tape_devices_entry->proc_iops = &tape_devices_inode_ops;
+#else
+ tape_devices_entry = (struct proc_dir_entry *) kmalloc
+ (sizeof (struct proc_dir_entry), GFP_ATOMIC);
+ if (tape_devices_entry) {
+ memset (tape_devices_entry, 0, sizeof (struct proc_dir_entry));
+ tape_devices_entry->name = "tapedevices";
+ tape_devices_entry->namelen = strlen ("tapedevices");
+ tape_devices_entry->low_ino = 0;
+ tape_devices_entry->mode = (S_IFREG | S_IRUGO | S_IWUSR);
+ tape_devices_entry->nlink = 1;
+ tape_devices_entry->uid = 0;
+ tape_devices_entry->gid = 0;
+ tape_devices_entry->size = 0;
+ tape_devices_entry->get_info = NULL;
+ tape_devices_entry->ops = &tape_devices_inode_ops;
+ proc_register (&proc_root, tape_devices_entry);
+ }
+#endif
+#endif /* CONFIG_PROC_FS */
+
+ return 0;
+}
+
+#ifdef MODULE
+MODULE_AUTHOR("(C) 2001 IBM Deutschland Entwicklung GmbH by Carsten Otte (cotte@de.ibm.com)");
+MODULE_DESCRIPTION("Linux for S/390 channel attached tape device driver");
+MODULE_PARM (tape, "1-" __MODULE_STRING (256) "s");
+
+int
+init_module (void)
+{
+#ifdef CONFIG_S390_TAPE_CHAR
+ tapechar_init ();
+#endif
+#ifdef CONFIG_S390_TAPE_BLOCK
+ tapeblock_init ();
+#endif
+ return 0;
+}
+
+void
+cleanup_module (void)
+{
+ tape_info_t *ti ,*temp;
+ tape_frontend_t* frontend, *tempfe;
+ tape_discipline_t* disc ,*tempdi;
+ int i;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"cleaup mod");
+#endif /* TAPE_DEBUG */
+
+ if (*tape) {
+ // we are running with parameters. we'll now deregister from our devno's
+ for (i=0;i<devregct;i++) {
+ s390_device_unregister(tape_devreg[devregct]);
+ }
+ }
+ ti = first_tape_info;
+ while (ti != NULL) {
+ temp = ti;
+ ti = ti->next;
+ //cleanup a device
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"free irq:");
+ debug_int_event (tape_debug_area,6,temp->devinfo.irq);
+#endif /* TAPE_DEBUG */
+ free_irq (temp->devinfo.irq, &(temp->devstat));
+ if (temp->discdata) kfree (temp->discdata);
+ if (temp->kernbuf) kfree (temp->kernbuf);
+ if (temp->cqr) tape_free_request(temp->cqr);
+#ifdef CONFIG_DEVFS_FS
+ for (frontend=first_frontend;frontend!=NULL;frontend=frontend->next)
+ frontend->rmdevfstree(temp);
+ tape_rmdevfsroots(temp);
+#endif
+ kfree (temp);
+ }
+#ifdef CONFIG_DEVFS_FS
+ devfs_unregister (tape_devfs_root_entry);
+#endif CONFIG_DEVFS_FS
+#ifdef CONFIG_PROC_FS
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+ remove_proc_entry ("tapedevices", &proc_root);
+#else
+ proc_unregister (&proc_root, tape_devices_entry->low_ino);
+ kfree (tape_devices_entry);
+#endif /* LINUX_IS_24 */
+#endif
+#ifdef CONFIG_S390_TAPE_CHAR
+ tapechar_uninit();
+#endif
+#ifdef CONFIG_S390_TAPE_BLOCK
+ tapeblock_uninit();
+#endif
+ frontend=first_frontend;
+ while (frontend != NULL) {
+ tempfe = frontend;
+ frontend = frontend->next;
+ kfree (tempfe);
+ }
+ disc=first_discipline;
+ while (disc != NULL) {
+ if (*tape)
+ disc->shutdown(0);
+ else
+ disc->shutdown(1);
+ tempdi = disc;
+ disc = disc->next;
+ kfree (tempdi);
+ }
+ /* Deallocate the local buffer for the ccwcache */
+ tape_cleanup_emergency_req ();
+#ifdef TAPE_DEBUG
+ debug_unregister (tape_debug_area);
+#endif /* TAPE_DEBUG */
+}
+#endif /* MODULE */
+
+inline void
+tapestate_set (tape_info_t * ti, int newstate)
+{
+ if (ti->tape_state == TS_NOT_OPER) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"ts_set err");
+ debug_text_exception (tape_debug_area,3,"dev n.oper");
+#endif /* TAPE_DEBUG */
+ } else {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,4,"ts. dev: ");
+ debug_int_event (tape_debug_area,4,ti->blk_minor);
+ debug_text_event (tape_debug_area,4,"old ts: ");
+ debug_text_event (tape_debug_area,4,(((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >=0 )) ?
+ state_verbose[tapestate_get (ti)] :
+ "UNKNOWN TS"));
+ debug_text_event (tape_debug_area,4,"new ts: ");
+ debug_text_event (tape_debug_area,4,(((newstate < TS_SIZE) &&
+ (newstate >= 0)) ?
+ state_verbose[newstate] :
+ "UNKNOWN TS"));
+#endif /* TAPE_DEBUG */
+ ti->tape_state = newstate;
+ }
+}
+
+inline int
+tapestate_get (tape_info_t * ti)
+{
+ return (ti->tape_state);
+}
+
+void
+tapestate_event (tape_info_t * ti, int event)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"te! dev: ");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+ debug_text_event (tape_debug_area,6,"event:");
+ debug_text_event (tape_debug_area,6,((event >=0) &&
+ (event < TE_SIZE)) ?
+ event_verbose[event] : "TE UNKNOWN");
+ debug_text_event (tape_debug_area,6,"state:");
+ debug_text_event (tape_debug_area,6,((tapestate_get(ti) >= 0) &&
+ (tapestate_get(ti) < TS_SIZE)) ?
+ state_verbose[tapestate_get (ti)] :
+ "TS UNKNOWN");
+#endif /* TAPE_DEBUG */
+ if (event == TE_ERROR) {
+ ti->discipline->error_recovery(ti);
+ } else {
+ if ((event >= 0) &&
+ (event < TE_SIZE) &&
+ (tapestate_get (ti) >= 0) &&
+ (tapestate_get (ti) < TS_SIZE) &&
+ ((*(ti->discipline->event_table))[tapestate_get (ti)][event] != NULL))
+ ((*(ti->discipline->event_table))[tapestate_get (ti)][event]) (ti);
+ else {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,3,"TE UNEXPEC");
+#endif /* TAPE_DEBUG */
+ ti->discipline->default_handler (ti);
+ }
+ }
+}
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only. This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */
--- /dev/null
+/***************************************************************************
+ *
+ * drivers/s390/char/tape.h
+ * tape device driver for 3480/3490E tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#ifndef _TAPE_H
+
+#define _TAPE_H
+#include <linux/config.h>
+#include <linux/blkdev.h>
+
+#define MAX_TAPES 7 /* Max tapes supported is 7*/
+#define TAPE_MAGIC 0xE3C1D7C5 /* is ebcdic-"TAPE" */
+
+typedef enum {
+ TS_UNUSED=0, TS_IDLE, TS_DONE, TS_FAILED,
+ TS_BLOCK_INIT,
+ TS_BSB_INIT,
+ TS_BSF_INIT,
+ TS_DSE_INIT,
+ TS_EGA_INIT,
+ TS_FSB_INIT,
+ TS_FSF_INIT,
+ TS_LDI_INIT,
+ TS_LBL_INIT,
+ TS_MSE_INIT,
+ TS_NOP_INIT,
+ TS_RBA_INIT,
+ TS_RBI_INIT,
+ TS_RBU_INIT,
+ TS_RBL_INIT,
+ TS_RDC_INIT,
+ TS_RFO_INIT,
+ TS_RSD_INIT,
+ TS_REW_INIT,
+ TS_REW_RELEASE_INIT,
+ TS_RUN_INIT,
+ TS_SEN_INIT,
+ TS_SID_INIT,
+ TS_SNP_INIT,
+ TS_SPG_INIT,
+ TS_SWI_INIT,
+ TS_SMR_INIT,
+ TS_SYN_INIT,
+ TS_TIO_INIT,
+ TS_UNA_INIT,
+ TS_WRI_INIT,
+ TS_WTM_INIT,
+ TS_NOT_OPER,
+ TS_SIZE } tape_stat;
+
+struct _tape_info_t; //Forward declaration
+
+typedef enum {
+ TE_START=0, TE_DONE, TE_FAILED, TE_ERROR, TE_OTHER,
+ TE_SIZE } tape_events;
+
+typedef void (*tape_disc_shutdown_t) (int);
+typedef void (*tape_event_handler_t) (struct _tape_info_t*);
+typedef ccw_req_t* (*tape_ccwgen_t)(struct _tape_info_t* ti,int count);
+typedef ccw_req_t* (*tape_reqgen_t)(struct request* req,struct _tape_info_t* ti,int tapeblock_major);
+typedef ccw_req_t* (*tape_rwblock_t)(const char* data,size_t count,struct _tape_info_t* ti);
+typedef void (*tape_freeblock_t)(ccw_req_t* cqr,struct _tape_info_t* ti);
+typedef void (*tape_setup_assist_t) (struct _tape_info_t*);
+#ifdef CONFIG_DEVFS_FS
+typedef void (*tape_devfs_handler_t) (struct _tape_info_t*);
+#endif
+typedef tape_event_handler_t tape_event_table_t[TS_SIZE][TE_SIZE];
+typedef struct _tape_discipline_t {
+ unsigned int cu_type;
+ tape_setup_assist_t setup_assist;
+ tape_event_handler_t error_recovery;
+ tape_reqgen_t bread;
+ tape_freeblock_t free_bread;
+ tape_rwblock_t write_block;
+ tape_freeblock_t free_write_block;
+ tape_rwblock_t read_block;
+ tape_freeblock_t free_read_block;
+ tape_ccwgen_t mtfsf;
+ tape_ccwgen_t mtbsf;
+ tape_ccwgen_t mtfsr;
+ tape_ccwgen_t mtbsr;
+ tape_ccwgen_t mtweof;
+ tape_ccwgen_t mtrew;
+ tape_ccwgen_t mtoffl;
+ tape_ccwgen_t mtnop;
+ tape_ccwgen_t mtbsfm;
+ tape_ccwgen_t mtfsfm;
+ tape_ccwgen_t mteom;
+ tape_ccwgen_t mterase;
+ tape_ccwgen_t mtsetdensity;
+ tape_ccwgen_t mtseek;
+ tape_ccwgen_t mttell;
+ tape_ccwgen_t mtsetdrvbuffer;
+ tape_ccwgen_t mtlock;
+ tape_ccwgen_t mtunlock;
+ tape_ccwgen_t mtload;
+ tape_ccwgen_t mtunload;
+ tape_ccwgen_t mtcompression;
+ tape_ccwgen_t mtsetpart;
+ tape_ccwgen_t mtmkpart;
+ tape_ccwgen_t mtiocget;
+ tape_ccwgen_t mtiocpos;
+ tape_disc_shutdown_t shutdown;
+ int (*discipline_ioctl_overload)(struct inode *,struct file*, unsigned int,unsigned long);
+ tape_event_table_t* event_table;
+ tape_event_handler_t default_handler;
+ struct _tape_info_t* tape; /* pointer for backreference */
+ void* next;
+} tape_discipline_t __attribute__ ((aligned(8)));
+
+typedef struct _tape_frontend_t {
+ tape_setup_assist_t device_setup;
+#ifdef CONFIG_DEVFS_FS
+ tape_devfs_handler_t mkdevfstree;
+ tape_devfs_handler_t rmdevfstree;
+#endif
+ void* next;
+} tape_frontend_t __attribute__ ((aligned(8)));
+
+
+typedef struct _tape_info_t {
+ wait_queue_head_t wq;
+ s390_dev_info_t devinfo; /* device info from Common I/O */
+ int wanna_wakeup;
+ int rew_minor; /* minor number for the rewinding tape */
+ int nor_minor; /* minor number for the nonrewinding tape */
+ int blk_minor; /* minor number for the block device */
+ devstat_t devstat; /* contains irq, devno, status */
+ size_t block_size; /* block size of tape */
+ int drive_type; /* Code indicating type of drive */
+ struct file *rew_filp; /* backpointer to file structure */
+ struct file *nor_filp;
+ struct file *blk_filp;
+ int tape_state; /* State of the device. See tape_stat */
+ int rc; /* Return code. */
+ tape_discipline_t* discipline;
+ request_queue_t request_queue;
+ struct request* current_request;
+ int blk_retries;
+ long position;
+ int medium_is_unloaded; // Becomes true when a unload-type operation was issued, false again when medium-insert was detected
+ ccw_req_t* cqr;
+ atomic_t bh_scheduled;
+ struct tq_struct bh_tq;
+#ifdef CONFIG_DEVFS_FS
+ devfs_handle_t devfs_dir; /* devfs handle for tape/DEVNO directory */
+ devfs_handle_t devfs_char_dir; /* devfs handle for tape/DEVNO/char directory */
+ devfs_handle_t devfs_block_dir; /* devfs handle for tape/DEVNO/block directory */
+ devfs_handle_t devfs_nonrewinding; /* devfs handle for tape/DEVNO/char/nonrewinding device */
+ devfs_handle_t devfs_rewinding; /* devfs handle for tape/DEVNO/char/rewinding device */
+ devfs_handle_t devfs_disc; /* devfs handle for tape/DEVNO/block/disc device */
+#endif
+ void* discdata;
+ void* kernbuf;
+ void* userbuf;
+ void* next;
+} tape_info_t __attribute__ ((aligned(8)));
+
+/* tape initialisation functions */
+int tape_init(void);
+int tape_setup (tape_info_t * ti, int irq, int minor);
+
+/* functoins for alloc'ing ccw stuff */
+inline ccw_req_t * tape_alloc_ccw_req (tape_info_t* ti, int cplength, int datasize);
+void tape_free_request (ccw_req_t * request);
+
+/* a function for dumping device sense info */
+void tape_dump_sense (devstat_t * stat);
+
+#ifdef CONFIG_S390_TAPE_DYNAMIC
+/* functions for dyn. dev. attach/detach */
+int tape_oper_handler ( int irq, struct _devreg *dreg);
+#endif
+
+/* functions for handling the status of a device */
+inline void tapestate_set (tape_info_t * ti, int newstate);
+inline int tapestate_get (tape_info_t * ti);
+void tapestate_event (tape_info_t * ti, int event);
+extern char* state_verbose[TS_SIZE];
+extern char* event_verbose[TE_SIZE];
+
+/****************************************************************************/
+
+/* Some linked lists for storing plugins and devices */
+extern tape_info_t *first_tape_info;
+extern tape_discipline_t *first_discipline;
+extern tape_frontend_t *first_frontend;
+
+/* The debug area */
+#ifdef TAPE_DEBUG
+extern debug_info_t *tape_debug_area;
+#endif
+
+#endif /* for ifdef tape.h */
--- /dev/null
+/***************************************************************************
+ *
+ * drivers/s390/char/tape3480.c
+ * tape device discipline for 3480 tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#include "tapedefs.h"
+#include <linux/version.h>
+#include <asm/ccwcache.h> /* CCW allocations */
+#include <asm/s390dyn.h>
+#include <asm/debug.h>
+#include <linux/compatmac.h>
+#include "tape.h"
+#include "tape34xx.h"
+#include "tape3480.h"
+
+tape_event_handler_t tape3480_event_handler_table[TS_SIZE][TE_SIZE] =
+{
+ /* {START , DONE, FAILED, ERROR, OTHER } */
+ {NULL, tape34xx_unused_done, NULL, NULL, NULL}, /* TS_UNUSED */
+ {NULL, tape34xx_idle_done, NULL, NULL, NULL}, /* TS_IDLE */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_DONE */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_FAILED */
+ {NULL, tape34xx_block_done, NULL, NULL, NULL}, /* TS_BLOCK_INIT */
+ {NULL, tape34xx_bsb_init_done, NULL, NULL, NULL}, /* TS_BSB_INIT */
+ {NULL, tape34xx_bsf_init_done, NULL, NULL, NULL}, /* TS_BSF_INIT */
+ {NULL, tape34xx_dse_init_done, NULL, NULL, NULL}, /* TS_DSE_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_EGA_INIT */
+ {NULL, tape34xx_fsb_init_done, NULL, NULL, NULL}, /* TS_FSB_INIT */
+ {NULL, tape34xx_fsf_init_done, NULL, NULL, NULL}, /* TS_FSF_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_LDI_INIT */
+ {NULL, tape34xx_lbl_init_done, NULL, NULL, NULL}, /* TS_LBL_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_MSE_INIT */
+ {NULL, tape34xx_nop_init_done, NULL, NULL, NULL}, /* TS_NOP_INIT */
+ {NULL, tape34xx_rfo_init_done, NULL, NULL, NULL}, /* TS_RBA_INIT */
+ {NULL, tape34xx_rbi_init_done, NULL, NULL, NULL}, /* TS_RBI_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBU_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBL_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RDC_INIT */
+ {NULL, tape34xx_rfo_init_done, NULL, NULL, NULL}, /* TS_RFO_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RSD_INIT */
+ {NULL, tape34xx_rew_init_done, NULL, NULL, NULL}, /* TS_REW_INIT */
+ {NULL, tape34xx_rew_release_init_done, NULL, NULL, NULL}, /* TS_REW_RELEASE_IMIT */
+ {NULL, tape34xx_run_init_done, NULL, NULL, NULL}, /* TS_RUN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SEN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SID_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SNP_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SPG_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SWI_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SMR_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SYN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_TIO_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_UNA_INIT */
+ {NULL, tape34xx_wri_init_done, NULL, NULL, NULL}, /* TS_WRI_INIT */
+ {NULL, tape34xx_wtm_init_done, NULL, NULL, NULL}, /* TS_WTM_INIT */
+ {NULL, NULL, NULL, NULL, NULL}}; /* TS_NOT_OPER */
+
+devreg_t tape3480_devreg = {
+ ci:
+ {hc:
+ {ctype:0x3480}},
+ flag:DEVREG_MATCH_CU_TYPE | DEVREG_TYPE_DEVCHARS,
+ oper_func:tape_oper_handler
+};
+
+
+void
+tape3480_setup_assist (tape_info_t * ti)
+{
+ tape3480_disc_data_t *data = NULL;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"3480 dsetu");
+ debug_text_event (tape_debug_area,6,"dev:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif /* TAPE_DEBUG */
+ while (data == NULL)
+ data = kmalloc (sizeof (tape3480_disc_data_t), GFP_KERNEL);
+ data->modeset_byte = 0x00;
+ ti->discdata = (void *) data;
+}
+
+
+void
+tape3480_shutdown (int autoprobe) {
+ if (autoprobe)
+ s390_device_unregister(&tape3480_devreg);
+}
+
+tape_discipline_t *
+tape3480_init (int autoprobe)
+{
+ tape_discipline_t *disc;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"3480 init");
+#endif /* TAPE_DEBUG */
+ disc = kmalloc (sizeof (tape_discipline_t), GFP_KERNEL);
+ if (disc == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,3,"disc:nomem");
+#endif /* TAPE_DEBUG */
+ return disc;
+ }
+ disc->cu_type = 0x3480;
+ disc->setup_assist = tape3480_setup_assist;
+ disc->error_recovery = tape34xx_error_recovery;
+ disc->write_block = tape34xx_write_block;
+ disc->free_write_block = tape34xx_free_write_block;
+ disc->read_block = tape34xx_read_block;
+ disc->free_read_block = tape34xx_free_read_block;
+ disc->mtfsf = tape34xx_mtfsf;
+ disc->mtbsf = tape34xx_mtbsf;
+ disc->mtfsr = tape34xx_mtfsr;
+ disc->mtbsr = tape34xx_mtbsr;
+ disc->mtweof = tape34xx_mtweof;
+ disc->mtrew = tape34xx_mtrew;
+ disc->mtoffl = tape34xx_mtoffl;
+ disc->mtnop = tape34xx_mtnop;
+ disc->mtbsfm = tape34xx_mtbsfm;
+ disc->mtfsfm = tape34xx_mtfsfm;
+ disc->mteom = tape34xx_mteom;
+ disc->mterase = tape34xx_mterase;
+ disc->mtsetdensity = tape34xx_mtsetdensity;
+ disc->mtseek = tape34xx_mtseek;
+ disc->mttell = tape34xx_mttell;
+ disc->mtsetdrvbuffer = tape34xx_mtsetdrvbuffer;
+ disc->mtlock = tape34xx_mtlock;
+ disc->mtunlock = tape34xx_mtunlock;
+ disc->mtload = tape34xx_mtload;
+ disc->mtunload = tape34xx_mtunload;
+ disc->mtcompression = tape34xx_mtcompression;
+ disc->mtsetpart = tape34xx_mtsetpart;
+ disc->mtmkpart = tape34xx_mtmkpart;
+ disc->mtiocget = tape34xx_mtiocget;
+ disc->mtiocpos = tape34xx_mtiocpos;
+ disc->shutdown = tape3480_shutdown;
+ disc->discipline_ioctl_overload = tape34xx_ioctl_overload;
+ disc->event_table = &tape3480_event_handler_table;
+ disc->default_handler = tape34xx_default_handler;
+ disc->bread = tape34xx_bread;
+ disc->free_bread = tape34xx_free_bread;
+ disc->tape = NULL; /* pointer for backreference */
+ disc->next = NULL;
+ if (autoprobe)
+ s390_device_register(&tape3480_devreg);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"3480 regis");
+#endif /* TAPE_DEBUG */
+ return disc;
+}
--- /dev/null
+/***************************************************************************
+ *
+ * drivers/s390/char/tape3480.h
+ * tape device discipline for 3480 tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#ifndef _TAPE3480_H
+
+#define _TAPE3480_H
+
+
+typedef struct _tape3480_disc_data_t {
+ __u8 modeset_byte;
+} tape3480_disc_data_t __attribute__ ((packed, aligned(8)));
+tape_discipline_t * tape3480_init (int);
+#endif // _TAPE3480_H
--- /dev/null
+/***************************************************************************
+ *
+ * drivers/s390/char/tape3490.c
+ * tape device discipline for 3490E tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#include "tapedefs.h"
+#include <linux/version.h>
+#include <asm/ccwcache.h> /* CCW allocations */
+#include <asm/s390dyn.h>
+#include <asm/debug.h>
+#include <linux/compatmac.h>
+#include "tape.h"
+#include "tape34xx.h"
+#include "tape3490.h"
+
+tape_event_handler_t tape3490_event_handler_table[TS_SIZE][TE_SIZE] =
+{
+ /* {START , DONE, FAILED, ERROR, OTHER } */
+ {NULL, tape34xx_unused_done, NULL, NULL, NULL}, /* TS_UNUSED */
+ {NULL, tape34xx_idle_done, NULL, NULL, NULL}, /* TS_IDLE */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_DONE */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_FAILED */
+ {NULL, tape34xx_block_done, NULL, NULL, NULL}, /* TS_BLOCK_INIT */
+ {NULL, tape34xx_bsb_init_done, NULL, NULL, NULL}, /* TS_BSB_INIT */
+ {NULL, tape34xx_bsf_init_done, NULL, NULL, NULL}, /* TS_BSF_INIT */
+ {NULL, tape34xx_dse_init_done, NULL, NULL, NULL}, /* TS_DSE_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_EGA_INIT */
+ {NULL, tape34xx_fsb_init_done, NULL, NULL, NULL}, /* TS_FSB_INIT */
+ {NULL, tape34xx_fsf_init_done, NULL, NULL, NULL}, /* TS_FSF_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_LDI_INIT */
+ {NULL, tape34xx_lbl_init_done, NULL, NULL, NULL}, /* TS_LBL_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_MSE_INIT */
+ {NULL, tape34xx_nop_init_done, NULL, NULL, NULL}, /* TS_NOP_INIT */
+ {NULL, tape34xx_rfo_init_done, NULL, NULL, NULL}, /* TS_RBA_INIT */
+ {NULL, tape34xx_rbi_init_done, NULL, NULL, NULL}, /* TS_RBI_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBU_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBL_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RDC_INIT */
+ {NULL, tape34xx_rfo_init_done, NULL, NULL, NULL}, /* TS_RFO_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RSD_INIT */
+ {NULL, tape34xx_rew_init_done, NULL, NULL, NULL}, /* TS_REW_INIT */
+ {NULL, tape34xx_rew_release_init_done, NULL, NULL, NULL}, /* TS_REW_RELEASE_IMIT */
+ {NULL, tape34xx_run_init_done, NULL, NULL, NULL}, /* TS_RUN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SEN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SID_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SNP_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SPG_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SWI_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SMR_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SYN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_TIO_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_UNA_INIT */
+ {NULL, tape34xx_wri_init_done, NULL, NULL, NULL}, /* TS_WRI_INIT */
+ {NULL, tape34xx_wtm_init_done, NULL, NULL, NULL}, /* TS_WTM_INIT */
+ {NULL, NULL, NULL, NULL, NULL}}; /* TS_NOT_OPER */
+
+devreg_t tape3490_devreg = {
+ ci:
+ {hc:
+ {ctype:0x3490}},
+ flag:DEVREG_MATCH_CU_TYPE | DEVREG_TYPE_DEVCHARS,
+ oper_func:tape_oper_handler
+};
+
+void
+tape3490_setup_assist (tape_info_t * ti)
+{
+ tape3490_disc_data_t *data = NULL;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"3490 dsetu");
+ debug_text_event (tape_debug_area,6,"dev:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif /* TAPE_DEBUG */
+ while (data == NULL)
+ data = kmalloc (sizeof (tape3490_disc_data_t), GFP_KERNEL);
+ data->modeset_byte = 0x00;
+ ti->discdata = (void *) data;
+}
+
+
+void
+tape3490_shutdown (int autoprobe) {
+ if (autoprobe)
+ s390_device_unregister(&tape3490_devreg);
+}
+
+
+tape_discipline_t *
+tape3490_init (int autoprobe)
+{
+ tape_discipline_t *disc;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"3490 init");
+#endif /* TAPE_DEBUG */
+ disc = kmalloc (sizeof (tape_discipline_t), GFP_KERNEL);
+ if (disc == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,3,"disc:nomem");
+#endif /* TAPE_DEBUG */
+ return disc;
+ }
+ disc->cu_type = 0x3490;
+ disc->setup_assist = tape3490_setup_assist;
+ disc->error_recovery = tape34xx_error_recovery;
+ disc->write_block = tape34xx_write_block;
+ disc->free_write_block = tape34xx_free_write_block;
+ disc->read_block = tape34xx_read_block;
+ disc->free_read_block = tape34xx_free_read_block;
+ disc->mtfsf = tape34xx_mtfsf;
+ disc->mtbsf = tape34xx_mtbsf;
+ disc->mtfsr = tape34xx_mtfsr;
+ disc->mtbsr = tape34xx_mtbsr;
+ disc->mtweof = tape34xx_mtweof;
+ disc->mtrew = tape34xx_mtrew;
+ disc->mtoffl = tape34xx_mtoffl;
+ disc->mtnop = tape34xx_mtnop;
+ disc->mtbsfm = tape34xx_mtbsfm;
+ disc->mtfsfm = tape34xx_mtfsfm;
+ disc->mteom = tape34xx_mteom;
+ disc->mterase = tape34xx_mterase;
+ disc->mtsetdensity = tape34xx_mtsetdensity;
+ disc->mtseek = tape34xx_mtseek;
+ disc->mttell = tape34xx_mttell;
+ disc->mtsetdrvbuffer = tape34xx_mtsetdrvbuffer;
+ disc->mtlock = tape34xx_mtlock;
+ disc->mtunlock = tape34xx_mtunlock;
+ disc->mtload = tape34xx_mtload;
+ disc->mtunload = tape34xx_mtunload;
+ disc->mtcompression = tape34xx_mtcompression;
+ disc->mtsetpart = tape34xx_mtsetpart;
+ disc->mtmkpart = tape34xx_mtmkpart;
+ disc->mtiocget = tape34xx_mtiocget;
+ disc->mtiocpos = tape34xx_mtiocpos;
+ disc->shutdown = tape3490_shutdown;
+ disc->discipline_ioctl_overload = tape34xx_ioctl_overload;
+ disc->event_table = &tape3490_event_handler_table;
+ disc->default_handler = tape34xx_default_handler;
+ disc->bread = tape34xx_bread;
+ disc->free_bread = tape34xx_free_bread;
+ disc->tape = NULL; /* pointer for backreference */
+ disc->next = NULL;
+ if (autoprobe)
+ s390_device_register(&tape3490_devreg);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"3490 regis");
+#endif /* TAPE_DEBUG */
+ return disc;
+}
--- /dev/null
+
+/***************************************************************************
+ *
+ * drivers/s390/char/tape3490.h
+ * tape device discipline for 3490E tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#ifndef _TAPE3490_H
+
+#define _TAPE3490_H
+
+
+typedef struct _tape3490_disc_data_t {
+ __u8 modeset_byte;
+} tape3490_disc_data_t __attribute__ ((packed, aligned(8)));
+tape_discipline_t * tape3490_init (int);
+#endif // _TAPE3490_H
--- /dev/null
+/***************************************************************************
+ *
+ * drivers/s390/char/tape34xx.c
+ * common tape device discipline for 34xx tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#include "tapedefs.h"
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <asm/types.h>
+#include <asm/uaccess.h>
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+#include <asm/ccwcache.h>
+#include <asm/idals.h>
+#ifdef CONFIG_S390_TAPE_DYNAMIC
+#include <asm/s390dyn.h>
+#endif
+#include <asm/debug.h>
+#include <linux/compatmac.h>
+#include "tape.h"
+#include "tape34xx.h"
+
+#define PRINTK_HEADER "T34xx:"
+
+tape_event_handler_t tape34xx_event_handler_table[TS_SIZE][TE_SIZE] =
+{
+ /* {START , DONE, FAILED, ERROR, OTHER } */
+ {NULL, tape34xx_unused_done, NULL, NULL, NULL}, /* TS_UNUSED */
+ {NULL, tape34xx_idle_done, NULL, NULL, NULL}, /* TS_IDLE */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_DONE */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_FAILED */
+ {NULL, tape34xx_block_done, NULL, NULL, NULL}, /* TS_BLOCK_INIT */
+ {NULL, tape34xx_bsb_init_done, NULL, NULL, NULL}, /* TS_BSB_INIT */
+ {NULL, tape34xx_bsf_init_done, NULL, NULL, NULL}, /* TS_BSF_INIT */
+ {NULL, tape34xx_dse_init_done, NULL, NULL, NULL}, /* TS_DSE_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_EGA_INIT */
+ {NULL, tape34xx_fsb_init_done, NULL, NULL, NULL}, /* TS_FSB_INIT */
+ {NULL, tape34xx_fsf_init_done, NULL, NULL, NULL}, /* TS_FSF_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_LDI_INIT */
+ {NULL, tape34xx_lbl_init_done, NULL, NULL, NULL}, /* TS_LBL_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_MSE_INIT */
+ {NULL, tape34xx_nop_init_done, NULL, NULL, NULL}, /* TS_NOP_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBA_INIT */
+ {NULL, tape34xx_rbi_init_done, NULL, NULL, NULL}, /* TS_RBI_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBU_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RBL_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RDC_INIT */
+ {NULL, tape34xx_rfo_init_done, NULL, NULL, NULL}, /* TS_RFO_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_RSD_INIT */
+ {NULL, tape34xx_rew_init_done, NULL, NULL, NULL}, /* TS_REW_INIT */
+ {NULL, tape34xx_rew_release_init_done, NULL, NULL, NULL}, /* TS_REW_RELEASE_IMIT */
+ {NULL, tape34xx_run_init_done, NULL, NULL, NULL}, /* TS_RUN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SEN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SID_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SNP_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SPG_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SWI_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SMR_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_SYN_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_TIO_INIT */
+ {NULL, NULL, NULL, NULL, NULL}, /* TS_UNA_INIT */
+ {NULL, tape34xx_wri_init_done, NULL, NULL, NULL}, /* TS_WRI_INIT */
+ {NULL, tape34xx_wtm_init_done, NULL, NULL, NULL}, /* TS_WTM_INIT */
+ {NULL, NULL, NULL, NULL, NULL}}; /* TS_NOT_OPER */
+
+
+int
+tape34xx_ioctl_overload (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return -EINVAL; // no additional ioctls
+
+}
+
+ccw_req_t *
+tape34xx_write_block (const char *data, size_t count, tape_info_t * ti)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ void *mem;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xwbl nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ mem = kmalloc (count, GFP_KERNEL);
+ if (!mem) {
+ tape_free_request (cqr);
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xwbl nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ if (copy_from_user (mem, data, count)) {
+ kfree (mem);
+ tape_free_request (cqr);
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xwbl segf.");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+
+ ccw->cmd_code = WRITE_CMD;
+ ccw->flags = 0;
+ ccw->count = count;
+ set_normalized_cda (ccw, (unsigned long) mem);
+ if ((ccw->cda) == 0) {
+ kfree (mem);
+ tape_free_request (cqr);
+ return NULL;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = mem;
+ ti->userbuf = (void *) data;
+ tapestate_set (ti, TS_WRI_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xwbl ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+void
+tape34xx_free_write_block (ccw_req_t * cqr, tape_info_t * ti)
+{
+ unsigned long lockflags;
+ ccw1_t *ccw;
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ccw = cqr->cpaddr;
+ ccw++;
+ clear_normalized_cda (ccw);
+ kfree (ti->kernbuf);
+ tape_free_request (cqr);
+ ti->kernbuf = ti->userbuf = NULL;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xfwb free");
+#endif /* TAPE_DEBUG */
+}
+
+ccw_req_t *
+tape34xx_read_block (const char *data, size_t count, tape_info_t * ti)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ void *mem;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xrbl nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ mem = kmalloc (count, GFP_KERNEL);
+ if (!mem) {
+ tape_free_request (cqr);
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xrbl nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+
+ ccw->cmd_code = READ_FORWARD;
+ ccw->flags = 0;
+ ccw->count = count;
+ set_normalized_cda (ccw, (unsigned long) mem);
+ if ((ccw->cda) == 0) {
+ kfree (mem);
+ tape_free_request (cqr);
+ return NULL;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = mem;
+ ti->userbuf = (void *) data;
+ tapestate_set (ti, TS_RFO_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xrbl ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+ccw_req_t *
+tape34xx_read_opposite (tape_info_t * ti,int novalue)
+{
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ size_t count;
+ // first, retrieve the count from the old cqr.
+ cqr = ti->cqr;
+ ccw = cqr->cpaddr;
+ ccw++;
+ count=ccw->count;
+ // free old cqr.
+ clear_normalized_cda (ccw);
+ tape_free_request (cqr);
+ // build new cqr
+ cqr = tape_alloc_ccw_req (ti, 3, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xrop nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+
+ ccw->cmd_code = READ_BACKWARD;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = count;
+ set_normalized_cda (ccw, (unsigned long) ti->kernbuf);
+ if ((ccw->cda) == 0) {
+ tape_free_request (cqr);
+ return NULL;
+ }
+ ccw++;
+ ccw->cmd_code = FORSPACEBLOCK;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ ccw->cda = (unsigned long)ccw;
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 1;
+ ccw->cda = (unsigned long)ccw;
+ tapestate_set (ti, TS_RBA_INIT);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xrop ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+void
+tape34xx_free_read_block (ccw_req_t * cqr, tape_info_t * ti)
+{
+ unsigned long lockflags;
+ size_t cpysize;
+ ccw1_t *ccw;
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ccw = cqr->cpaddr;
+ ccw++;
+ cpysize = ccw->count - ti->devstat.rescnt;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ if (copy_to_user (ti->userbuf, ti->kernbuf, cpysize)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfrb segf.");
+#endif /* TAPE_DEBUG */
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ clear_normalized_cda (ccw);
+ kfree (ti->kernbuf);
+ tape_free_request (cqr);
+ ti->kernbuf = ti->userbuf = NULL;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xfrb free");
+#endif /* TAPE_DEBUG */
+}
+
+/*
+ * The IOCTL interface is implemented in the following section,
+ * excepted the MTRESET, MTSETBLK which are handled by tapechar.c
+ */
+/*
+ * MTFSF: Forward space over 'count' file marks. The tape is positioned
+ * at the EOT (End of Tape) side of the file mark.
+ */
+ccw_req_t *
+tape34xx_mtfsf (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfsf parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfsf nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = FORSPACEFILE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_FSF_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xfsf ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTBSF: Backward space over 'count' file marks. The tape is positioned at
+ * the EOT (End of Tape) side of the last skipped file mark.
+ */
+ccw_req_t *
+tape34xx_mtbsf (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbsf parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbsf nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = BACKSPACEFILE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_BSF_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xbsf ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTFSR: Forward space over 'count' tape blocks (blocksize is set
+ * via MTSETBLK.
+ */
+ccw_req_t *
+tape34xx_mtfsr (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfsr parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfsr nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = FORSPACEBLOCK;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_FSB_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xfsr ccwgen");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTBSR: Backward space over 'count' tape blocks.
+ * (blocksize is set via MTSETBLK.
+ */
+ccw_req_t *
+tape34xx_mtbsr (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbsr parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbsr nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = BACKSPACEBLOCK;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_BSB_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xbsr ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTWEOF: Write 'count' file marks at the current position.
+ */
+ccw_req_t *
+tape34xx_mtweof (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xweo parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xweo nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = WRITETAPEMARK;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_WTM_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xweo ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTREW: Rewind the tape.
+ */
+ccw_req_t *
+tape34xx_mtrew (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 3, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xrew nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = REWIND;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_REW_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xrew ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTOFFL: Rewind the tape and put the drive off-line.
+ * Implement 'rewind unload'
+ */
+ccw_req_t *
+tape34xx_mtoffl (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 3, 32);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xoff nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = REWIND_UNLOAD;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = SENSE;
+ ccw->flags = 0;
+ ccw->count = 32;
+ ccw->cda = (unsigned long) cqr->cpaddr;
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_RUN_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xoff ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTNOP: 'No operation'.
+ */
+ccw_req_t *
+tape34xx_mtnop (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 1, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xnop nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) ccw->cmd_code;
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xnop ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTBSFM: Backward space over 'count' file marks.
+ * The tape is positioned at the BOT (Begin Of Tape) side of the
+ * last skipped file mark.
+ */
+ccw_req_t *
+tape34xx_mtbsfm (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbsm parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbsm nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = BACKSPACEFILE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_BSF_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xbsm ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTFSFM: Forward space over 'count' file marks.
+ * The tape is positioned at the BOT (Begin Of Tape) side
+ * of the last skipped file mark.
+ */
+ccw_req_t *
+tape34xx_mtfsfm (tape_info_t * ti, int count)
+{
+ long lockflags;
+ int i;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count == 0) || (count > 510)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfsm parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ cqr = tape_alloc_ccw_req (ti, 2 + count, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xfsm nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ for (i = 0; i < count; i++) {
+ ccw->cmd_code = FORSPACEFILE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ }
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_FSF_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xfsm ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTEOM: positions at the end of the portion of the tape already used
+ * for recordind data. MTEOM positions after the last file mark, ready for
+ * appending another file.
+ * MTRETEN: Retension the tape, i.e. forward space to end of tape and rewind.
+ */
+ccw_req_t *
+tape34xx_mteom (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 4, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xeom nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = FORSPACEFILE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = CCW_CMD_TIC;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (cqr->cpaddr);
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_FSF_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xeom ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTERASE: erases the tape.
+ */
+ccw_req_t *
+tape34xx_mterase (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 5, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xera nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = REWIND;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = ERASE_GAP;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = DATA_SEC_ERASE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_DSE_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xera ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTSETDENSITY: set tape density.
+ */
+ccw_req_t *
+tape34xx_mtsetdensity (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xden nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xden ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTSEEK: seek to the specified block.
+ */
+ccw_req_t *
+tape34xx_mtseek (tape_info_t * ti, int count)
+{
+ long lockflags;
+ __u8 *data;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((data = kmalloc (4 * sizeof (__u8), GFP_KERNEL)) == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xsee nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ data[0] = 0x01;
+ data[1] = data[2] = data[3] = 0x00;
+ if (count >= 4194304) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xsee parm");
+#endif /* TAPE_DEBUG */
+ kfree(data);
+ return NULL;
+ }
+ if (((tape34xx_disc_data_t *) ti->discdata)->modeset_byte & 0x08) // IDRC on
+
+ data[1] = data[1] | 0x80;
+ data[3] += count % 256;
+ data[2] += (count / 256) % 256;
+ data[1] += (count / 65536);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xsee id:");
+ debug_int_event (tape_debug_area,6,count);
+#endif /* TAPE_DEBUG */
+ cqr = tape_alloc_ccw_req (ti, 3, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xsee nomem");
+#endif /* TAPE_DEBUG */
+ kfree (data);
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = LOCATE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 4;
+ set_normalized_cda (ccw, (unsigned long) data);
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = data;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_LBL_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xsee ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTTELL: Tell block. Return the number of block relative to current file.
+ */
+ccw_req_t *
+tape34xx_mttell (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ void *mem;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xtel nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ mem = kmalloc (8, GFP_KERNEL);
+ if (!mem) {
+ tape_free_request (cqr);
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xtel nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+
+ ccw->cmd_code = READ_BLOCK_ID;
+ ccw->flags = 0;
+ ccw->count = 8;
+ set_normalized_cda (ccw, (unsigned long) mem);
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = mem;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_RBI_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xtel ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTSETDRVBUFFER: Set the tape drive buffer code to number.
+ * Implement NOP.
+ */
+ccw_req_t *
+tape34xx_mtsetdrvbuffer (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xbuf nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xbuf ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTLOCK: Locks the tape drive door.
+ * Implement NOP CCW command.
+ */
+ccw_req_t *
+tape34xx_mtlock (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xloc nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xloc ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTUNLOCK: Unlocks the tape drive door.
+ * Implement the NOP CCW command.
+ */
+ccw_req_t *
+tape34xx_mtunlock (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xulk nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xulk ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTLOAD: Loads the tape.
+ * This function is not implemented and returns NULL, which causes the Frontend to wait for a medium being loaded.
+ * The 3480/3490 type Tapes do not support a load command
+ */
+ccw_req_t *
+tape34xx_mtload (tape_info_t * ti, int count)
+{
+ return NULL;
+}
+
+/*
+ * MTUNLOAD: Rewind the tape and unload it.
+ */
+ccw_req_t *
+tape34xx_mtunload (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 3, 32);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xunl nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = REWIND_UNLOAD;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ccw++;
+ ccw->cmd_code = SENSE;
+ ccw->flags = 0;
+ ccw->count = 32;
+ ccw->cda = (unsigned long) cqr->cpaddr;
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_RUN_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xunl ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTCOMPRESSION: used to enable compression.
+ * Sets the IDRC on/off.
+ */
+ccw_req_t *
+tape34xx_mtcompression (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ if ((count < 0) || (count > 1)) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xcom parm");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ if (count == 0)
+ ((tape34xx_disc_data_t *) ti->discdata)->modeset_byte = 0x00; // IDRC off
+
+ else
+ ((tape34xx_disc_data_t *) ti->discdata)->modeset_byte = 0x08; // IDRC on
+
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xcom nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xcom ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTSTPART: Move the tape head at the partition with the number 'count'.
+ * Implement the NOP CCW command.
+ */
+ccw_req_t *
+tape34xx_mtsetpart (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xspa nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xspa ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTMKPART: .... dummy .
+ * Implement the NOP CCW command.
+ */
+ccw_req_t *
+tape34xx_mtmkpart (tape_info_t * ti, int count)
+{
+ long lockflags;
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ cqr = tape_alloc_ccw_req (ti, 2, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xnpa nomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = NULL;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_NOP_INIT);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xnpa ccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+
+/*
+ * MTIOCGET: query the tape drive status.
+ */
+ccw_req_t *
+tape34xx_mtiocget (tape_info_t * ti, int count)
+{
+ return NULL;
+}
+
+/*
+ * MTIOCPOS: query the tape position.
+ */
+ccw_req_t *
+tape34xx_mtiocpos (tape_info_t * ti, int count)
+{
+ return NULL;
+}
+
+ccw_req_t * tape34xx_bread (struct request *req,tape_info_t* ti,int tapeblock_major) {
+ ccw_req_t *cqr;
+ ccw1_t *ccw;
+ __u8 *data;
+ int s2b = blksize_size[tapeblock_major][ti->blk_minor]/hardsect_size[tapeblock_major][ti->blk_minor];
+ int realcount;
+ int size,bhct = 0;
+ struct buffer_head* bh;
+ for (bh = req->bh; bh; bh = bh->b_reqnext) {
+ if (bh->b_size > blksize_size[tapeblock_major][ti->blk_minor])
+ for (size = 0; size < bh->b_size; size += blksize_size[tapeblock_major][ti->blk_minor])
+ bhct++;
+ else
+ bhct++;
+ }
+ if ((data = kmalloc (4 * sizeof (__u8), GFP_ATOMIC)) == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,3,"xBREDnomem");
+#endif /* TAPE_DEBUG */
+ return NULL;
+ }
+ data[0] = 0x01;
+ data[1] = data[2] = data[3] = 0x00;
+ realcount=req->sector/s2b;
+ if (((tape34xx_disc_data_t *) ti->discdata)->modeset_byte & 0x08) // IDRC on
+
+ data[1] = data[1] | 0x80;
+ data[3] += realcount % 256;
+ data[2] += (realcount / 256) % 256;
+ data[1] += (realcount / 65536);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xBREDid:");
+ debug_int_event (tape_debug_area,6,realcount);
+#endif /* TAPE_DEBUG */
+ cqr = tape_alloc_ccw_req (ti, 2+bhct+1, 0);
+ if (!cqr) {
+#ifdef TAPE_DEBUG
+ debug_text_exception (tape_debug_area,6,"xBREDnomem");
+#endif /* TAPE_DEBUG */
+ kfree(data);
+ return NULL;
+ }
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = MODE_SET_DB;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 1;
+ set_normalized_cda (ccw, (unsigned long) (&(((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)));
+ if (realcount!=ti->position) {
+ ccw++;
+ ccw->cmd_code = LOCATE;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 4;
+ set_normalized_cda (ccw, (unsigned long) data);
+ }
+ ti->position=realcount+req->nr_sectors/s2b;
+ for (bh=req->bh;bh!=NULL;) {
+ ccw->flags = CCW_FLAG_CC;
+ if (bh->b_size >= blksize_size[tapeblock_major][ti->blk_minor]) {
+ for (size = 0; size < bh->b_size; size += blksize_size[tapeblock_major][ti->blk_minor]) {
+ ccw++;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->cmd_code = READ_FORWARD;
+ ccw->count = blksize_size[tapeblock_major][ti->blk_minor];
+ set_normalized_cda (ccw, __pa (bh->b_data + size));
+ }
+ bh = bh->b_reqnext;
+ } else { /* group N bhs to fit into byt_per_blk */
+ for (size = 0; bh != NULL && size < blksize_size[tapeblock_major][ti->blk_minor];) {
+ ccw++;
+ ccw->flags = CCW_FLAG_DC;
+ ccw->cmd_code = READ_FORWARD;
+ ccw->count = bh->b_size;
+ set_normalized_cda (ccw, __pa (bh->b_data));
+ size += bh->b_size;
+ bh = bh->b_reqnext;
+ }
+ if (size != blksize_size[tapeblock_major][ti->blk_minor]) {
+ PRINT_WARN ("Cannot fulfill small request %d vs. %d (%ld sects)\n",
+ size,
+ blksize_size[tapeblock_major][ti->blk_minor],
+ req->nr_sectors);
+ kfree(data);
+ tape_free_request (cqr);
+ return NULL;
+ }
+ }
+ }
+ ccw -> flags &= ~(CCW_FLAG_DC);
+ ccw -> flags |= (CCW_FLAG_CC);
+ ccw++;
+ ccw->cmd_code = NOP;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (unsigned long) (&(ccw->cmd_code));
+ ti->kernbuf = data;
+ ti->userbuf = NULL;
+ tapestate_set (ti, TS_BLOCK_INIT);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xBREDccwg");
+#endif /* TAPE_DEBUG */
+ return cqr;
+}
+void tape34xx_free_bread (ccw_req_t* cqr,struct _tape_info_t* ti) {
+ ccw1_t* ccw;
+ for (ccw=(ccw1_t*)cqr->cpaddr;(ccw->flags & CCW_FLAG_CC)||(ccw->flags & CCW_FLAG_DC);ccw++)
+ if ((ccw->cmd_code == MODE_SET_DB) ||
+ (ccw->cmd_code == LOCATE) ||
+ (ccw->cmd_code == READ_FORWARD))
+ clear_normalized_cda(ccw);
+ tape_free_request(cqr);
+ kfree(ti->kernbuf);
+ ti->kernbuf=NULL;
+}
+
+/* event handlers */
+void
+tape34xx_default_handler (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xdefhandle");
+#endif /* TAPE_DEBUG */
+ PRINT_ERR ("TAPE34XX: An unexpected Unit Check occurred.\n");
+ PRINT_ERR ("TAPE34XX: Please read Documentation/s390/TAPE and report it!\n");
+ PRINT_ERR ("TAPE34XX: Current state is: %s",
+ (((tapestate_get (ti) < TS_SIZE) && (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] : "->UNKNOWN STATE<-"));
+ tape_dump_sense (&ti->devstat);
+ ti->rc = -EIO;
+ ti->wanna_wakeup=1;
+ switch (tapestate_get(ti)) {
+ case TS_REW_RELEASE_INIT:
+ tapestate_set(ti,TS_FAILED);
+ wake_up (&ti->wq);
+ break;
+ case TS_BLOCK_INIT:
+ tapestate_set(ti,TS_FAILED);
+ schedule_tapeblock_exec_IO(ti);
+ break;
+ default:
+ tapestate_set(ti,TS_FAILED);
+ wake_up_interruptible (&ti->wq);
+ }
+}
+
+void
+tape34xx_unexpect_uchk_handler (tape_info_t * ti)
+{
+ if ((ti->devstat.ii.sense.data[0] == 0x40) &&
+ (ti->devstat.ii.sense.data[1] == 0x40) &&
+ (ti->devstat.ii.sense.data[3] == 0x43)) {
+ // no tape in the drive
+ PRINT_INFO ("Drive %d not ready. No volume loaded.\n", ti->rew_minor / 2);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"xuuh nomed");
+#endif /* TAPE_DEBUG */
+ tapestate_set (ti, TS_FAILED);
+ ti->rc = -ENOMEDIUM;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+ } else if ((ti->devstat.ii.sense.data[0] == 0x42) &&
+ (ti->devstat.ii.sense.data[1] == 0x44) &&
+ (ti->devstat.ii.sense.data[3] == 0x3b)) {
+ PRINT_INFO ("Media in drive %d was changed!\n",
+ ti->rew_minor / 2);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"xuuh medchg");
+#endif
+ /* nothing to do. chan end & dev end will be reported when io is finished */
+ } else {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"xuuh unexp");
+ debug_text_event (tape_debug_area,3,"state:");
+ debug_text_event (tape_debug_area,3,((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] :
+ "TS UNKNOWN");
+#endif /* TAPE_DEBUG */
+ tape34xx_default_handler (ti);
+ }
+}
+
+void
+tape34xx_unused_done (tape_info_t * ti)
+{
+ if (ti->medium_is_unloaded) {
+ // A medium was inserted in the drive!
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xuui med");
+#endif /* TAPE_DEBUG */
+ PRINT_WARN ("A medium was inserted into the tape.\n");
+ ti->medium_is_unloaded=0;
+ } else {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"unsol.irq!");
+ debug_text_event (tape_debug_area,3,"dev end");
+ debug_int_exception (tape_debug_area,3,ti->devinfo.irq);
+#endif /* TAPE_DEBUG */
+ PRINT_WARN ("Unsolicited IRQ (Device End) caught in unused state.\n");
+ tape_dump_sense (&ti->devstat);
+ }
+}
+
+
+void
+tape34xx_idle_done (tape_info_t * ti)
+{
+ if (ti->medium_is_unloaded) {
+ // A medium was inserted in the drive!
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"xuud med");
+#endif /* TAPE_DEBUG */
+ PRINT_WARN ("A medium was inserted into the tape.\n");
+ ti->medium_is_unloaded=0;
+ wake_up_interruptible (&ti->wq);
+ } else {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"unsol.irq!");
+ debug_text_event (tape_debug_area,3,"dev end");
+ debug_int_exception (tape_debug_area,3,ti->devinfo.irq);
+#endif /* TAPE_DEBUG */
+ PRINT_WARN ("Unsolicited IRQ (Device End) caught in idle state.\n");
+ tape_dump_sense (&ti->devstat);
+ }
+}
+
+void
+tape34xx_block_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"x:bREQdone");
+#endif /* TAPE_DEBUG */
+ tapestate_set(ti,TS_DONE);
+ schedule_tapeblock_exec_IO(ti);
+}
+
+void
+tape34xx_bsf_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"bsf done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_dse_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"dse done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_fsf_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"fsf done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_fsb_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"fsb done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_bsb_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"bsb done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_lbl_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"lbl done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ //s390irq_spin_unlock(tape->devinfo.irq);
+ ti->wanna_wakeup=1;
+ wake_up (&ti->wq);
+}
+
+void
+tape34xx_nop_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"nop done..");
+ debug_text_exception (tape_debug_area,6,"or rew/rel");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ //s390irq_spin_unlock(tape->devinfo.irq);
+ ti->wanna_wakeup=1;
+ wake_up (&ti->wq);
+}
+
+void
+tape34xx_rfo_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"rfo done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_rbi_init_done (tape_info_t * ti)
+{
+ __u8 *data;
+#ifdef TAPE_DEBUG
+ int i;
+#endif
+ tapestate_set (ti, TS_FAILED);
+ data = ti->kernbuf;
+ ti->rc = data[3];
+ ti->rc += 256 * data[2];
+ ti->rc += 65536 * (data[1] & 0x3F);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"rbi done");
+ debug_text_event (tape_debug_area,6,"data:");
+ for (i=0;i<8;i++)
+ debug_int_event (tape_debug_area,6,data[i]);
+#endif
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_rew_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"rew done");
+#endif
+ //BH: use irqsave
+ //s390irq_spin_lock(tape->devinfo.irq);
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ //s390irq_spin_unlock(tape->devinfo.irq);
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_rew_release_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"rewR done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ //s390irq_spin_unlock(tape->devinfo.irq);
+ ti->wanna_wakeup=1;
+ wake_up (&ti->wq);
+}
+
+void
+tape34xx_run_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"rew done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_wri_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"wri done");
+#endif
+ //BH: use irqsave
+ //s390irq_spin_lock(ti->devinfo.irq);
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ //s390irq_spin_unlock(ti->devinfo.irq);
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+void
+tape34xx_wtm_init_done (tape_info_t * ti)
+{
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"wtm done");
+#endif
+ tapestate_set (ti, TS_DONE);
+ ti->rc = 0;
+ ti->wanna_wakeup=1;
+ wake_up_interruptible (&ti->wq);
+}
+
+/* This function analyses the tape's sense-data in case of a unit-check. If possible,
+ it tries to recover from the error. Else the user is informed about the problem. */
+void
+tape34xx_error_recovery (tape_info_t* ti)
+{
+ __u8* sense=ti->devstat.ii.sense.data;
+ int inhibit_cu_recovery=0;
+ int cu_type=ti->discipline->cu_type;
+ if ((((tape34xx_disc_data_t *) ti->discdata)->modeset_byte)&0x80) inhibit_cu_recovery=1;
+ if (tapestate_get(ti)==TS_BLOCK_INIT) {
+ // no recovery for block device, bottom half will retry...
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ }
+ if (sense[0]&SENSE_COMMAND_REJECT)
+ switch (tapestate_get(ti)) {
+ case TS_BLOCK_INIT:
+ case TS_DSE_INIT:
+ case TS_EGA_INIT:
+ case TS_WRI_INIT:
+ case TS_WTM_INIT:
+ if (sense[1]&SENSE_WRITE_PROTECT) {
+ // trying to write, but medium is write protected
+ tape34xx_error_recovery_has_failed(ti,EACCES);
+ return;
+ }
+ default:
+ tape34xx_error_recovery_HWBUG(ti,1);
+ return;
+ }
+ // special cases for various tape-states when reaching end of recorded area
+ if (((sense[0]==0x08) || (sense[0]==0x10) || (sense[0]==0x12)) &&
+ ((sense[1]==0x40) || (sense[1]==0x0c)))
+ switch (tapestate_get(ti)) {
+ case TS_FSF_INIT:
+ // Trying to seek beyond end of recorded area
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case TS_LBL_INIT:
+ // Block could not be located.
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case TS_RFO_INIT:
+ // Try to read beyond end of recorded area -> 0 bytes read
+ tape34xx_error_recovery_has_failed(ti,0);
+ return;
+ }
+ // Sensing special bits
+ if (sense[0]&SENSE_BUS_OUT_CHECK) {
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ }
+ if (sense[0]&SENSE_DATA_CHECK) {
+ // hardware failure, damaged tape or improper operating conditions
+ switch (sense[3]) {
+ case 0x23:
+ // a read data check occurred
+ if ((sense[2]&SENSE_TAPE_SYNC_MODE) ||
+ (inhibit_cu_recovery)) {
+ // data check is not permanent, may be recovered.
+ // We always use async-mode with cu-recovery, so this should *never* happen.
+ tape34xx_error_recovery_HWBUG(ti,2);
+ return;
+ } else {
+ // data check is permanent, CU recovery has failed
+ PRINT_WARN("Permanent read error, recovery failed!\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ }
+ case 0x25:
+ // a write data check occurred
+ if ((sense[2]&SENSE_TAPE_SYNC_MODE) ||
+ (inhibit_cu_recovery)) {
+ // data check is not permanent, may be recovered.
+ // We always use async-mode with cu-recovery, so this should *never* happen.
+ tape34xx_error_recovery_HWBUG(ti,3);
+ return;
+ } else {
+ // data check is permanent, cu-recovery has failed
+ PRINT_WARN("Permanent write error, recovery failed!\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ }
+ case 0x26:
+ // Data Check (read opposite) occurred. We'll recover this.
+ tape34xx_error_recovery_read_opposite(ti);
+ return;
+ case 0x28:
+ // The ID-Mark at the beginning of the tape could not be written. This is fatal, we'll report and exit.
+ PRINT_WARN("ID-Mark could not be written. Check your hardware!\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x31:
+ // Tape void. Tried to read beyond end of device. We'll report and exit.
+ PRINT_WARN("Try to read beyond end of recorded area!\n");
+ tape34xx_error_recovery_has_failed(ti,ENOSPC);
+ return;
+ case 0x41:
+ // Record sequence error. cu detected incorrect block-id sequence on tape. We'll report and exit.
+ PRINT_WARN("Illegal block-id sequence found!\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ default:
+ // well, all data checks for 3480 should result in one of the above erpa-codes. if not -> bug
+ // On 3490, other data-check conditions do exist.
+ if (cu_type==0x3480) {
+ tape34xx_error_recovery_HWBUG(ti,4);
+ return;
+ }
+ }
+ }
+ if (sense[0]&SENSE_OVERRUN) {
+ // A data overrun between cu and drive occurred. The channel speed is to slow! We'll report this and exit!
+ switch (sense[3]) {
+ case 0x40: // overrun error
+ PRINT_WARN ("Data overrun error between control-unit and drive. Use a faster channel connection, if possible! \n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ default:
+ // Overrun bit is set, but erpa does not show overrun error. This is a bug.
+ tape34xx_error_recovery_HWBUG(ti,5);
+ return;
+ }
+ }
+ if (sense[1]&SENSE_RECORD_SEQUENCE_ERR) {
+ switch (sense[3]) {
+ case 0x41:
+ // Record sequence error. cu detected incorrect block-id sequence on tape. We'll report and exit.
+ PRINT_WARN("Illegal block-id sequence found!\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ default:
+ // Record sequence error bit is set, but erpa does not show record sequence error. This is a bug.
+ tape34xx_error_recovery_HWBUG(ti,6);
+ return;
+ }
+ }
+ // Sensing erpa codes
+ switch (sense[3]) {
+ case 0x00:
+ // Everything is fine, but we got a unit check. Report and ignore!
+ PRINT_WARN ("Non-error sense was found. Unit-check will be ignored, expect errors...\n");
+ return;
+ case 0x21:
+ // Data streaming not operational. Cu switches to interlock mode, we reissue the command.
+ PRINT_WARN ("Data streaming not operational. Switching to interlock-mode! \n");
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x22:
+ // Path equipment check. Might be drive adapter error, buffer error on the lower interface, internal path not useable, or error during cartridge load.
+ // All of the above are not recoverable
+ PRINT_WARN ("A path equipment check occurred. One of the following conditions occurred:\n");
+ PRINT_WARN ("drive adapter error,buffer error on the lower interface, internal path not useable, error during cartridge load.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x23:
+ // Read data check. Should have been be covered earlier -> Bug!
+ tape34xx_error_recovery_HWBUG(ti,7);
+ return;
+ case 0x24:
+ // Load display check. Load display was command was issued, but the drive is displaying a drive check message. Can be threated as "device end".
+ tape34xx_error_recovery_succeded(ti);
+ return;
+ case 0x25:
+ // Write data check. Should have been covered earlier -> Bug!
+ tape34xx_error_recovery_HWBUG(ti,8);
+ return;
+ case 0x26:
+ // Data check (read opposite). Should have been covered earlier -> Bug!
+ tape34xx_error_recovery_HWBUG(ti,9);
+ return;
+ case 0x27:
+ // Command reject. May indicate illegal channel program or buffer over/underrun.
+ // Since all channel programms are issued by this driver and ought be correct,
+ // we assume a over/underrun situaltion and retry the channel program.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x28:
+ // Write id mark check. Should have beed covered earlier -> bug!
+ tape34xx_error_recovery_HWBUG(ti,10);
+ return;
+ case 0x29:
+ // Function incompatible. Either idrc is on but hardware not capable doing idrc
+ // or a perform subsystem func is issued and the cu is not online. Anyway, this
+ // cannot be recovered and is an I/O error.
+ PRINT_WARN ("Function incompatible. Try to switch off idrc! \n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x2a:
+ // Unsolicited environmental data. An internal counter overflows, we can ignore
+ // this and reissue the cmd.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x2b:
+ // Environmental data present. Indicates either unload completed ok or read buffered
+ // log command completed ok.
+ if (tapestate_get(ti)==TS_RUN_INIT) {
+ // Rewind unload completed ok.
+ tape34xx_error_recovery_succeded(ti);
+ return;
+ }
+ // Since we do not issue read buffered log commands, this should never occur -> bug.
+ tape34xx_error_recovery_HWBUG(ti,11);
+ return;
+ case 0x2c:
+ // Permanent equipment check. cu has tried recovery, but did not succeed. This is an
+ // I/O error.
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x2d:
+ // Data security erase failure.
+ if (tapestate_get(ti)==TS_DSE_INIT) {
+ // report an I/O error
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ }
+ // Data security erase failure, but no such command issued. This is a bug.
+ tape34xx_error_recovery_HWBUG(ti,12);
+ return;
+ case 0x2e:
+ // Not capable. This indicates either that the drive fails reading the format id mark
+ // or that that format specified is not supported by the drive. We write a message and
+ // return an I/O error.
+ PRINT_WARN("Drive not capable processing the tape format!");
+ tape34xx_error_recovery_has_failed(ti,EMEDIUMTYPE);
+ return;
+ case 0x2f:
+ // This erpa is reserved. This is a bug.
+ tape34xx_error_recovery_HWBUG(ti,13);
+ return;
+ case 0x30:
+ // The medium is write protected, while trying to write on it. We'll report this.
+ PRINT_WARN("Medium is write protected!\n");
+ tape34xx_error_recovery_has_failed(ti,EACCES);
+ return;
+ case 0x31:
+ // Tape void. Should have beed covered ealier -> bug
+ tape34xx_error_recovery_HWBUG(ti,14);
+ return;
+ case 0x32:
+ // Tension loss. We cannot recover this, it's an I/O error.
+ PRINT_WARN("The drive lost tape tension.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x33:
+ // Load Failure. The catridge was not inserted correctly or the tape is not threaded
+ // correctly. We cannot recover this, the user has to reload the catridge.
+ PRINT_WARN("Cartridge load failure. Reload the cartridge and try again.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x34:
+ // Unload failure. The drive cannot maintain tape tension and control tape movement
+ // during an unload operation.
+ PRINT_WARN("Failure during cartridge unload. Please try manually.\n");
+ if (tapestate_get(ti)!=TS_RUN_INIT) {
+ tape34xx_error_recovery_HWBUG(ti,15);
+ return;
+ }
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x35:
+ // Drive equipment check. One of the following:
+ // - cu cannot recover from a drive detected error
+ // - a check code message is displayed on drive message/load displays
+ // - the cartridge loader does not respond correctly
+ // - a failure occurs during an index, load, or unload cycle
+ PRINT_WARN("Equipment check! Please check the drive and the cartridge loader.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x36:
+ switch (cu_type) {
+ case 0x3480:
+ // This erpa is reserved for 3480 -> BUG
+ tape34xx_error_recovery_HWBUG(ti,16);
+ return;
+ case 0x3490:
+ // End of data. This is a permanent I/O error, which cannot be recovered.
+ // A read-type command has reached the end-of-data mark.
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ }
+ case 0x37:
+ // Tape length error. The tape is shorter than reported in the beginning-of-tape data.
+ PRINT_WARN("Tape length error.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x38:
+ // Physical end of tape. A read/write operation reached the physical end of tape.
+ if (tapestate_get(ti)==TS_WRI_INIT) {
+ tape34xx_error_recovery_has_failed(ti,ENOSPC);
+ }
+ return;
+ case 0x39:
+ // Backward at BOT. The drive is at BOT and is requestet to move backward.
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x3a:
+ // Drive switched not ready, but the command needs the drive to be ready.
+ PRINT_WARN("Drive not ready. Turn the ready/not ready switch to ready position and try again.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x3b:
+ // Manual rewind or unload. This causes an I/O error.
+ PRINT_WARN("Medium was rewound or unloaded manually. Expect errors! Please do only use the mtoffl and mtrew ioctl to unload tapes or rewind tapes.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x3c:
+ case 0x3d:
+ case 0x3e:
+ case 0x3f:
+ // These erpas are reserved -> BUG
+ tape34xx_error_recovery_HWBUG(ti,17);
+ return;
+ case 0x40:
+ // Overrun error. This should have been covered earlier -> bug.
+ tape34xx_error_recovery_HWBUG(ti,18);
+ return;
+ case 0x41:
+ // Record sequence error. This should have been covered earlier -> bug.
+ tape34xx_error_recovery_HWBUG(ti,19);
+ return;
+ case 0x42:
+ // Degraded mode. A condition that can cause degraded performace is detected.
+ PRINT_WARN("Subsystem is running in degraded mode. This may compromise your performace.\n");
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x43:
+ // Drive not ready. Probably swith the ready/not ready switch to ready?
+ PRINT_WARN("The drive is not ready. Maybe no medium in?\n");
+ tape34xx_error_recovery_has_failed(ti,ENOMEDIUM);
+ return;
+ case 0x44:
+ // Locate Block unsuccessfull. We'll report this.
+ if ((tapestate_get(ti)!=TS_BLOCK_INIT) &&
+ (tapestate_get(ti)!=TS_LBL_INIT)) {
+ tape34xx_error_recovery_HWBUG(ti,20); // No locate block was issued...
+ return;
+ }
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x45:
+ // The drive is assigned elsewhere [to a different channel path/computer].
+ PRINT_WARN("The drive is assigned elsewhere.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x46:
+ // Drive not online. Drive may be switched offline, the power supply may be switched off
+ // or the drive address may not be set correctly.
+ PRINT_WARN("The drive is not online.");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x47:
+ // Volume fenced. cu reports volume integrity is lost!
+ PRINT_WARN("Volume fenced. The volume integrity is lost! \n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x48:
+ // Log sense data and retry request. We'll do so...
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x49:
+ // Bus out check. A parity check error on the bus was found. PRINT_WARN("Bus out check. A data transfer over the bus was corrupted.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x4a:
+ // Control unit erp failed. We'll report this.
+ PRINT_WARN("The control unit failed recovering an I/O error.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x4b:
+ // Cu and drive incompatible. The drive requests micro-program patches, which are not available on the cu.
+ PRINT_WARN("The drive needs microprogram patches from the control unit, which are not available.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x4c:
+ // Recovered Check-One failure. Cu develops a hardware error, but is able to recover. We'll reissue the command.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x4d:
+ switch (cu_type) {
+ case 0x3480:
+ // This erpa is reserved for 3480 -> bug
+ tape34xx_error_recovery_HWBUG(ti,21);
+ return;
+ case 0x3490:
+ // Resetting event recieved. Since the driver does not support resetting event recovery
+ // (which has to be handled by the I/O Layer), we'll report and retry our command.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ }
+ case 0x4e:
+ switch (cu_type) {
+ case 0x3480:
+ // This erpa is reserved for 3480 -> bug.
+ tape34xx_error_recovery_HWBUG(ti,22);
+ return;
+ case 0x3490:
+ // Maximum block size exeeded. This indicates, that the block to be written is larger
+ // than allowed for buffered mode. We'll report this...
+ PRINT_WARN("Maximum block size for buffered mode exceeded.\n");
+ tape34xx_error_recovery_has_failed(ti,ENOBUFS);
+ return;
+ }
+ case 0x4f:
+ // These erpas are reserved -> bug
+ tape34xx_error_recovery_HWBUG(ti,23);
+ return;
+ case 0x50:
+ // Read buffered log (Overflow). Cu is running in extended beffered log mode, and a counter overflows.
+ // This should never happen, since we're never running in extended buffered log mode -> bug.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x51:
+ // Read buffered log (EOV). EOF processing occurs while the cu is in extended buffered log mode.
+ // This should never happen, since we're never running in extended buffered log mode -> bug.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x52:
+ // End of Volume complete. Rewind unload completed ok. We'll report to the user...
+ if (tapestate_get(ti)!=TS_RUN_INIT) {
+ tape34xx_error_recovery_HWBUG(ti,24);
+ return;
+ }
+ tape34xx_error_recovery_succeded(ti);
+ return;
+ case 0x53:
+ // Global command intercept. We'll have to reissue our command.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x54:
+ // Channel interface recovery (temporary). This can be recovered by reissuing the command.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x55:
+ // Channel interface recovery (permanent). This cannot be recovered, we'll inform the user.
+ PRINT_WARN("A permanent channel interface error occurred.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x56:
+ // Channel protocol error. This cannot be recovered.
+ PRINT_WARN("A channel protocol error occurred.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x57:
+ switch (cu_type) {
+ case 0x3480:
+ // Attention intercept. We have to reissue the command.
+ PRINT_WARN("An attention intercept occurred, which will be recovered.\n");
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ case 0x3490:
+ // Global status intercept. We have to reissue the command.
+ PRINT_WARN("An global status intercept was recieved, which will be recovered.\n");
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ }
+ case 0x58:
+ case 0x59:
+ // These erpas are reserved -> bug.
+ tape34xx_error_recovery_HWBUG(ti,25);
+ return;
+ case 0x5a:
+ // Tape length incompatible. The tape inserted is too long,
+ // which could cause damage to the tape or the drive.
+ PRINT_WARN("Tape length incompatible [should be IBM Cartridge System Tape]. May cause damage to drive or tape.n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x5b:
+ // Format 3480 XF incompatible
+ if (sense[1]&SENSE_BEGINNING_OF_TAPE) {
+ // Everything is fine. The tape will be overwritten in a different format.
+ tape34xx_error_recovery_do_retry(ti);
+ return;
+ }
+ PRINT_WARN("Tape format is incompatible to the drive, which writes 3480-2 XF.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x5c:
+ // Format 3480-2 XF incompatible
+ PRINT_WARN("Tape format is incompatible to the drive. The drive cannot access 3480-2 XF volumes.\n");
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ return;
+ case 0x5d:
+ // Tape length violation.
+ PRINT_WARN("Tape length violation [should be IBM Enhanced Capacity Cartridge System Tape]. May cause damage to drive or tape.\n");
+ tape34xx_error_recovery_has_failed(ti,EMEDIUMTYPE);
+ return;
+ case 0x5e:
+ // Compaction algorithm incompatible.
+ PRINT_WARN("The volume is recorded using an incompatible compaction algorith, which is not supported by the control unit.\n");
+ tape34xx_error_recovery_has_failed(ti,EMEDIUMTYPE);
+ return;
+ default:
+ // Reserved erpas -> bug
+ tape34xx_error_recovery_HWBUG(ti,26);
+ return;
+ }
+}
+
+void tape34xx_error_recovery_has_failed (tape_info_t* ti,int error_id) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"xerp fail");
+ debug_text_event (tape_debug_area,3,(((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] : "UNKNOWN"));
+#endif
+ if ((tapestate_get(ti)!=TS_UNUSED) && (tapestate_get(ti)!=TS_IDLE)) {
+ tape_dump_sense(&ti->devstat);
+ ti->rc = -error_id;
+ ti->wanna_wakeup=1;
+ switch (tapestate_get(ti)) {
+ case TS_REW_RELEASE_INIT:
+ tapestate_set(ti,TS_FAILED);
+ wake_up (&ti->wq);
+ break;
+ case TS_BLOCK_INIT:
+ tapestate_set(ti,TS_FAILED);
+ schedule_tapeblock_exec_IO(ti);
+ break;
+ default:
+ tapestate_set(ti,TS_FAILED);
+ wake_up_interruptible (&ti->wq);
+ }
+ } else {
+ PRINT_WARN("Recieved an unsolicited IRQ.\n");
+ tape_dump_sense(&ti->devstat);
+ }
+}
+
+void tape34xx_error_recovery_succeded(tape_info_t* ti) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"xerp done");
+ debug_text_event (tape_debug_area,3,(((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] : "UNKNOWN"));
+#endif
+ if ((tapestate_get(ti)!=TS_UNUSED) && (tapestate_get(ti)!=TS_DONE)) {
+ tapestate_event (ti, TE_DONE);
+ } else {
+ PRINT_WARN("Recieved an unsolicited IRQ.\n");
+ tape_dump_sense(&ti->devstat);
+ }
+}
+
+void tape34xx_error_recovery_do_retry(tape_info_t* ti) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"xerp retr");
+ debug_text_event (tape_debug_area,3,(((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] : "UNKNOWN"));
+#endif
+ if ((tapestate_get(ti)!=TS_UNUSED) && (tapestate_get(ti)!=TS_IDLE)) {
+ tape_dump_sense(&ti->devstat);
+ while (do_IO (ti->devinfo.irq, ti->cqr->cpaddr, (unsigned long) ti->cqr, 0x00, ti->cqr->options));
+ } else {
+ PRINT_WARN("Recieved an unsolicited IRQ.\n");
+ tape_dump_sense(&ti->devstat);
+ }
+}
+
+void
+tape34xx_error_recovery_read_opposite (tape_info_t* ti) {
+ switch (tapestate_get(ti)) {
+ case TS_RFO_INIT:
+ // We did read forward, but the data could not be read *correctly*.
+ // We will read backward and then skip forward again.
+ ti->cqr=tape34xx_read_opposite(ti,0);
+ if (ti->cqr==NULL)
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ else
+ tape34xx_error_recovery_do_retry(ti);
+ break;
+ case TS_RBA_INIT:
+ // We tried to read forward and backward, but hat no success -> failed.
+ tape34xx_error_recovery_has_failed(ti,EIO);
+ break;
+ case TS_BLOCK_INIT:
+ tape34xx_error_recovery_do_retry(ti);
+ break;
+ default:
+ PRINT_WARN("read_opposite_recovery_called_with_state:%s\n",
+ (((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] : "UNKNOWN"));
+ }
+}
+
+void
+tape34xx_error_recovery_HWBUG (tape_info_t* ti,int condno) {
+ devstat_t* stat=&ti->devstat;
+ PRINT_WARN("An unexpected condition #%d was caught in tape error recovery.\n",condno);
+ PRINT_WARN("Please report this incident.\n");
+ PRINT_WARN("State of the tape:%s\n",
+ (((tapestate_get (ti) < TS_SIZE) &&
+ (tapestate_get (ti) >= 0)) ?
+ state_verbose[tapestate_get (ti)] : "UNKNOWN"));
+ PRINT_INFO ("Sense data: %02X%02X%02X%02X %02X%02X%02X%02X "
+ " %02X%02X%02X%02X %02X%02X%02X%02X \n",
+ stat->ii.sense.data[0], stat->ii.sense.data[1],
+ stat->ii.sense.data[2], stat->ii.sense.data[3],
+ stat->ii.sense.data[4], stat->ii.sense.data[5],
+ stat->ii.sense.data[6], stat->ii.sense.data[7],
+ stat->ii.sense.data[8], stat->ii.sense.data[9],
+ stat->ii.sense.data[10], stat->ii.sense.data[11],
+ stat->ii.sense.data[12], stat->ii.sense.data[13],
+ stat->ii.sense.data[14], stat->ii.sense.data[15]);
+ PRINT_INFO ("Sense data: %02X%02X%02X%02X %02X%02X%02X%02X "
+ " %02X%02X%02X%02X %02X%02X%02X%02X \n",
+ stat->ii.sense.data[16], stat->ii.sense.data[17],
+ stat->ii.sense.data[18], stat->ii.sense.data[19],
+ stat->ii.sense.data[20], stat->ii.sense.data[21],
+ stat->ii.sense.data[22], stat->ii.sense.data[23],
+ stat->ii.sense.data[24], stat->ii.sense.data[25],
+ stat->ii.sense.data[26], stat->ii.sense.data[27],
+ stat->ii.sense.data[28], stat->ii.sense.data[29],
+ stat->ii.sense.data[30], stat->ii.sense.data[31]);
+ tape34xx_error_recovery_has_failed(ti,EIO);
+}
--- /dev/null
+
+/***************************************************************************
+ *
+ * drivers/s390/char/tape34xx.h
+ * common tape device discipline for 34xx tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ ****************************************************************************
+ */
+
+#ifndef _TAPE34XX_H
+
+#define _TAPE34XX_H
+
+/*
+ * The CCW commands for the Tape type of command.
+ */
+
+#define INVALID_00 0x00 /* Invalid cmd */
+#define BACKSPACEBLOCK 0x27 /* Back Space block */
+#define BACKSPACEFILE 0x2f /* Back Space file */
+#define DATA_SEC_ERASE 0x97 /* Data security erase */
+#define ERASE_GAP 0x17 /* Erase Gap */
+#define FORSPACEBLOCK 0x37 /* Forward space block */
+#define FORSPACEFILE 0x3F /* Forward Space file */
+#define FORCE_STREAM_CNT 0xEB /* Forced streaming count # */
+#define NOP 0x03 /* No operation */
+#define READ_FORWARD 0x02 /* Read forward */
+#define REWIND 0x07 /* Rewind */
+#define REWIND_UNLOAD 0x0F /* Rewind and Unload */
+#define SENSE 0x04 /* Sense */
+#define NEW_MODE_SET 0xEB /* Guess it is Mode set */
+#define WRITE_CMD 0x01 /* Write */
+#define WRITETAPEMARK 0x1F /* Write Tape Mark */
+
+#define ASSIGN 0xB7 /* 3420 REJECT,3480 OK */
+#define CONTROL_ACCESS 0xE3 /* Set high speed */
+#define DIAG_MODE_SET 0x0B /* 3420 NOP, 3480 REJECT*/
+#define LOAD_DISPLAY 0x9F /* 3420 REJECT,3480 OK */
+#define LOCATE 0x4F /* 3420 REJ, 3480 NOP */
+#define LOOP_WRITE_TO_READ 0x8B /* 3480 REJECT */
+#define MODE_SET_DB 0xDB /* 3420 REJECT,3480 OK */
+#define MODE_SET_C3 0xC3 /* for 3420 */
+#define MODE_SET_CB 0xCB /* for 3420 */
+#define MODE_SET_D3 0xD3 /* for 3420 */
+#define READ_BACKWARD 0x0C /* */
+#define READ_BLOCK_ID 0x22 /* 3420 REJECT,3480 OK */
+#define READ_BUFFER 0x12 /* 3420 REJECT,3480 OK */
+#define READ_BUFF_LOG 0x24 /* 3420 REJECT,3480 OK */
+#define RELEASE 0xD4 /* 3420 NOP, 3480 REJECT*/
+#define REQ_TRK_IN_ERROR 0x1B /* 3420 NOP, 3480 REJECT*/
+#define RESERVE 0xF4 /* 3420 NOP, 3480 REJECT*/
+#define SENSE_GROUP_ID 0x34 /* 3420 REJECT,3480 OK */
+#define SENSE_ID 0xE4 /* 3420 REJECT,3480 OK */
+#define READ_DEV_CHAR 0x64 /* Read device characteristics */
+#define SET_DIAGNOSE 0x4B /* 3420 NOP, 3480 REJECT*/
+#define SET_GROUP_ID 0xAF /* 3420 REJECT,3480 OK */
+#define SET_TAPE_WRITE_IMMED 0xC3 /* for 3480 */
+#define SUSPEND 0x5B /* 3420 REJ, 3480 NOP */
+#define SYNC 0x43 /* Synchronize (flush buffer) */
+#define UNASSIGN 0xC7 /* 3420 REJECT,3480 OK */
+#define PERF_SUBSYS_FUNC 0x77 /* 3490 CMD */
+#define READ_CONFIG_DATA 0xFA /* 3490 CMD */
+#define READ_MESSAGE_ID 0x4E /* 3490 CMD */
+#define READ_SUBSYS_DATA 0x3E /* 3490 CMD */
+#define SET_INTERFACE_ID 0x73 /* 3490 CMD */
+
+#ifndef MIN
+#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
+#endif
+
+
+#define BLOCKSIZE 4096 /* size of the tape rcds */
+
+#define COMMAND_CHAIN CCW_FLAG_CC /* redefine from irq.h */
+#define CHANNEL_END DEV_STAT_CHN_END /* redefine from irq.h */
+#define DEVICE_END DEV_STAT_DEV_END /* redefine from irq.h */
+#define UNIT_CHECK DEV_STAT_UNIT_CHECK /* redefine from irq.h */
+#define UNIT_EXCEPTION DEV_STAT_UNIT_EXCEP /* redefine from irq.h */
+#define CONTROL_UNIT_END DEV_STAT_CU_END /* redefine from irq.h */
+#define INCORR_LEN SCHN_STAT_INCORR_LEN /* redefine from irq.h */
+
+#define SENSE_COMMAND_REJECT 0x80
+#define SENSE_INTERVENTION_REQUIRED 0x40
+#define SENSE_BUS_OUT_CHECK 0x20
+#define SENSE_EQUIPMENT_CHECK 0x10
+#define SENSE_DATA_CHECK 0x08
+#define SENSE_OVERRUN 0x04
+#define SENSE_DEFERRED_UNIT_CHECK 0x02
+#define SENSE_ASSIGNED_ELSEWHERE 0x01
+
+#define SENSE_LOCATE_FAILURE 0x80
+#define SENSE_DRIVE_ONLINE 0x40
+#define SENSE_RESERVED 0x20
+#define SENSE_RECORD_SEQUENCE_ERR 0x10
+#define SENSE_BEGINNING_OF_TAPE 0x08
+#define SENSE_WRITE_MODE 0x04
+#define SENSE_WRITE_PROTECT 0x02
+#define SENSE_NOT_CAPABLE 0x01
+
+#define SENSE_CHANNEL_ADAPTER_CODE 0xE0
+#define SENSE_CHANNEL_ADAPTER_LOC 0x10
+#define SENSE_REPORTING_CU 0x08
+#define SENSE_AUTOMATIC_LOADER 0x04
+#define SENSE_TAPE_SYNC_MODE 0x02
+#define SENSE_TAPE_POSITIONING 0x01
+
+typedef struct _tape34xx_disc_data_t {
+ __u8 modeset_byte;
+} tape34xx_disc_data_t __attribute__ ((packed, aligned(8)));
+
+/* discipline functions */
+int tape34xx_ioctl_overload (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
+ccw_req_t * tape34xx_write_block (const char *data, size_t count, tape_info_t * ti);
+void tape34xx_free_write_block (ccw_req_t * cqr, tape_info_t * ti);
+ccw_req_t * tape34xx_read_block (const char *data, size_t count, tape_info_t * ti);
+void tape34xx_free_read_block (ccw_req_t * cqr, tape_info_t * ti);
+void tape34xx_clear_read_block (ccw_req_t * cqr, tape_info_t * ti);
+ccw_req_t * tape34xx_mtfsf (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtbsf (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtfsr (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtbsr (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtweof (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtrew (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtoffl (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtnop (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtbsfm (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtfsfm (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mteom (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mterase (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtsetdensity (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtseek (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mttell (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtsetdrvbuffer (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtlock (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtunlock (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtload (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtunload (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtcompression (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtsetpart (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtmkpart (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtiocget (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_mtiocpos (tape_info_t * ti, int count);
+ccw_req_t * tape34xx_bread (struct request *req, tape_info_t* ti,int tapeblock_major);
+ccw_req_t * tape34xx_bwrite (struct request *req, tape_info_t* ti,int tapeblock_major);
+void tape34xx_free_bread (ccw_req_t*,struct _tape_info_t*);
+void tape34xx_free_bwrite (ccw_req_t*,struct _tape_info_t*);
+
+/* Event handlers */
+void tape34xx_default_handler (tape_info_t * ti);
+void tape34xx_unexpect_uchk_handler (tape_info_t * ti);
+void tape34xx_unused_done(tape_info_t* ti);
+void tape34xx_idle_done(tape_info_t* ti);
+void tape34xx_block_done(tape_info_t* ti);
+void tape34xx_bsf_init_done(tape_info_t* ti);
+void tape34xx_dse_init_done(tape_info_t* ti);
+void tape34xx_fsf_init_done(tape_info_t* ti);
+void tape34xx_bsb_init_done(tape_info_t* ti);
+void tape34xx_fsb_init_done(tape_info_t* ti);
+void tape34xx_lbl_init_done(tape_info_t* ti);
+void tape34xx_nop_init_done(tape_info_t* ti);
+void tape34xx_rfo_init_done(tape_info_t* ti);
+void tape34xx_rbi_init_done(tape_info_t* ti);
+void tape34xx_rew_init_done(tape_info_t* ti);
+void tape34xx_rew_release_init_done(tape_info_t* ti);
+void tape34xx_run_init_done(tape_info_t* ti);
+void tape34xx_wri_init_done(tape_info_t* ti);
+void tape34xx_wtm_init_done(tape_info_t* ti);
+
+extern void schedule_tapeblock_exec_IO (tape_info_t *ti);
+
+// the error recovery stuff:
+void tape34xx_error_recovery (tape_info_t* ti);
+void tape34xx_error_recovery_has_failed (tape_info_t* ti,int error_id);
+void tape34xx_error_recovery_succeded(tape_info_t* ti);
+void tape34xx_error_recovery_do_retry(tape_info_t* ti);
+void tape34xx_error_recovery_read_opposite (tape_info_t* ti);
+void tape34xx_error_recovery_HWBUG (tape_info_t* ti,int condno);
+#endif // _TAPE34XX_H
--- /dev/null
+
+/***************************************************************************
+ *
+ * drivers/s390/char/tapeblock.c
+ * block device frontend for tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ *
+ ****************************************************************************
+ */
+
+#include "tapedefs.h"
+#include <linux/config.h>
+#include <linux/blkdev.h>
+#include <linux/blk.h>
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <asm/ccwcache.h> /* CCW allocations */
+#include <asm/debug.h>
+#include <asm/s390dyn.h>
+#include <linux/compatmac.h>
+#ifdef MODULE
+#define __NO_VERSION__
+#include <linux/module.h>
+#endif
+#include "tape.h"
+#include "tapeblock.h"
+
+#define PRINTK_HEADER "TBLOCK:"
+
+/*
+ * file operation structure for tape devices
+ */
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+static struct block_device_operations tapeblock_fops = {
+#else
+static struct file_operations tapeblock_fops = {
+#endif
+ open : tapeblock_open, /* open */
+ release : tapeblock_release, /* release */
+ };
+
+int tapeblock_major = 0;
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+static void tape_request_fn (request_queue_t * queue);
+#else
+static void tape_request_fn (void);
+#endif
+
+static request_queue_t* tapeblock_getqueue (kdev_t kdev);
+
+#ifdef CONFIG_DEVFS_FS
+void
+tapeblock_mkdevfstree (tape_info_t* ti) {
+ ti->devfs_block_dir=devfs_mk_dir (ti->devfs_dir, "block", ti);
+ ti->devfs_disc=devfs_register(ti->devfs_block_dir, "disc",DEVFS_FL_DEFAULT,
+ tapeblock_major, ti->blk_minor,
+ TAPEBLOCK_DEFAULTMODE, &tapeblock_fops, ti);
+}
+
+void
+tapeblock_rmdevfstree (tape_info_t* ti) {
+ devfs_unregister(ti->devfs_disc);
+ devfs_unregister(ti->devfs_block_dir);
+}
+#endif
+
+void
+tapeblock_setup(tape_info_t* ti) {
+ blk_size[tapeblock_major][ti->blk_minor]=0; // this will be detected
+ blksize_size[tapeblock_major][ti->blk_minor]=2048; // blocks are 2k by default.
+ hardsect_size[tapeblock_major][ti->blk_minor]=512;
+ blk_init_queue (&ti->request_queue, tape_request_fn);
+ blk_queue_headactive (&ti->request_queue, 0);
+#ifdef CONFIG_DEVFS_FS
+ tapeblock_mkdevfstree(ti);
+#endif
+}
+
+int
+tapeblock_init(void) {
+ int result;
+ tape_frontend_t* blkfront,*temp;
+ tape_info_t* ti;
+
+ tape_init();
+ /* Register the tape major number to the kernel */
+#ifdef CONFIG_DEVFS_FS
+ result = devfs_register_blkdev(tapeblock_major, "tBLK", &tapeblock_fops);
+#else
+ result = register_blkdev(tapeblock_major, "tBLK", &tapeblock_fops);
+#endif
+ if (result < 0) {
+ PRINT_WARN(KERN_ERR "tape: can't get major %d for block device\n", tapeblock_major);
+ panic ("cannot get major number for tape block device");
+ }
+ if (tapeblock_major == 0) tapeblock_major = result; /* accept dynamic major number*/
+ INIT_BLK_DEV(tapeblock_major,tape_request_fn,tapeblock_getqueue,NULL);
+ read_ahead[tapeblock_major]=TAPEBLOCK_READAHEAD;
+ PRINT_WARN(KERN_ERR " tape gets major %d for block device\n", result);
+ blk_size[tapeblock_major] = (int*) kmalloc (256*sizeof(int),GFP_ATOMIC);
+ memset(blk_size[tapeblock_major],0,256*sizeof(int));
+ blksize_size[tapeblock_major] = (int*) kmalloc (256*sizeof(int),GFP_ATOMIC);
+ memset(blksize_size[tapeblock_major],0,256*sizeof(int));
+ hardsect_size[tapeblock_major] = (int*) kmalloc (256*sizeof(int),GFP_ATOMIC);
+ memset(hardsect_size[tapeblock_major],0,256*sizeof(int));
+ max_sectors[tapeblock_major] = (int*) kmalloc (256*sizeof(int),GFP_ATOMIC);
+ memset(max_sectors[tapeblock_major],0,256*sizeof(int));
+ blkfront = kmalloc(sizeof(tape_frontend_t),GFP_KERNEL);
+ if (blkfront==NULL) panic ("no mem for tape block device structure");
+ blkfront->device_setup=tapeblock_setup;
+#ifdef CONFIG_DEVFS_FS
+ blkfront->mkdevfstree = tapeblock_mkdevfstree;
+ blkfront->rmdevfstree = tapeblock_rmdevfstree;
+#endif
+ blkfront->next=NULL;
+ if (first_frontend==NULL) {
+ first_frontend=blkfront;
+ } else {
+ temp=first_frontend;
+ while (temp->next!=NULL)
+ temp=temp->next;
+ temp->next=blkfront;
+ }
+ ti=first_tape_info;
+ while (ti!=NULL) {
+ tapeblock_setup(ti);
+ ti=ti->next;
+ }
+ return 0;
+}
+
+
+void
+tapeblock_uninit(void) {
+ unregister_blkdev(tapeblock_major, "tBLK");
+}
+
+int
+tapeblock_open(struct inode *inode, struct file *filp) {
+ tape_info_t *ti;
+ kdev_t dev;
+ int rc;
+ long lockflags;
+
+ inode = filp->f_dentry->d_inode;
+ ti = first_tape_info;
+ while ((ti != NULL) && (ti->blk_minor != MINOR (inode->i_rdev)))
+ ti = (tape_info_t *) ti->next;
+ if (ti == NULL)
+ return -ENODEV;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:open:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) != TS_UNUSED) {
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:dbusy");
+#endif
+ return -EBUSY;
+ }
+ tapestate_set (ti, TS_IDLE);
+ ti->position=-1;
+
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ rc=tapeblock_mediumdetect(ti);
+ if (rc) {
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ tapestate_set (ti, TS_UNUSED);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return rc; // in case of errors, we don't have a size of the medium
+ }
+ dev = MKDEV (tapeblock_major, MINOR (inode->i_rdev)); /* Get the device */
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->blk_filp = filp;
+ filp->private_data = ti; /* save the dev.info for later reference */
+ ti->cqr=NULL;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif /* MODULE */
+ return 0;
+}
+
+int
+tapeblock_release(struct inode *inode, struct file *filp) {
+ long lockflags;
+ tape_info_t *ti,*lastti;
+ ti = first_tape_info;
+ while ((ti != NULL) && (ti->blk_minor != MINOR (inode->i_rdev)))
+ ti = (tape_info_t *) ti->next;
+ if ((ti != NULL) && (tapestate_get (ti) == TS_NOT_OPER)) {
+ if (ti==first_tape_info) {
+ first_tape_info=ti->next;
+ } else {
+ lastti=first_tape_info;
+ while (lastti->next!=ti) lastti=lastti->next;
+ lastti->next=ti->next;
+ }
+ kfree(ti);
+ return 0;
+ }
+ if ((ti == NULL) || (tapestate_get (ti) != TS_IDLE)) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"b:notidle!");
+#endif
+ return -ENXIO; /* error in tape_release */
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:release:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ tapestate_set (ti, TS_UNUSED);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif /* MODULE */
+ invalidate_buffers(inode->i_rdev);
+ return 0;
+}
+
+static void
+tapeblock_end_request(tape_info_t* ti) {
+ struct buffer_head *bh;
+ int uptodate;
+ if ((tapestate_get(ti)!=TS_FAILED) &&
+ (tapestate_get(ti)!=TS_DONE))
+ BUG(); // A request has to be completed to end it
+ uptodate=(tapestate_get(ti)==TS_DONE); // is the buffer up to date?
+#ifdef TAPE_DEBUG
+ if (uptodate) {
+ debug_text_event (tape_debug_area,6,"b:done:");
+ debug_int_event (tape_debug_area,6,(long)ti->cqr);
+ } else {
+ debug_text_event (tape_debug_area,3,"b:failed:");
+ debug_int_event (tape_debug_area,3,(long)ti->cqr);
+ }
+#endif
+ // now inform ll_rw_block about a request status
+ while ((bh = ti->current_request->bh) != NULL) {
+ ti->current_request->bh = bh->b_reqnext;
+ bh->b_reqnext = NULL;
+ bh->b_end_io (bh, uptodate);
+ }
+ if (!end_that_request_first (ti->current_request, uptodate, "tBLK")) {
+#ifndef DEVICE_NO_RANDOM
+ add_blkdev_randomness (MAJOR (ti->current_request->rq_dev));
+#endif
+ end_that_request_last (ti->current_request);
+ }
+ ti->discipline->free_bread(ti->cqr,ti);
+ ti->cqr=NULL;
+ ti->current_request=NULL;
+ if (tapestate_get(ti)!=TS_NOT_OPER) tapestate_set(ti,TS_IDLE);
+ return;
+}
+
+static void
+tapeblock_exec_IO (tape_info_t* ti) {
+ int rc;
+ struct request* req;
+ if (ti->cqr) { // process done/failed request
+ while ((tapestate_get(ti)==TS_FAILED) &&
+ ti->blk_retries>0) {
+ ti->blk_retries--;
+ ti->position=-1;
+ tapestate_set(ti,TS_BLOCK_INIT);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"b:retryreq:");
+ debug_int_event (tape_debug_area,3,(long)ti->cqr);
+#endif
+ rc = do_IO (ti->devinfo.irq, ti->cqr->cpaddr, (unsigned long) ti->cqr,
+ 0x00, ti->cqr->options);
+ if (rc) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"b:doIOfail:");
+ debug_int_event (tape_debug_area,3,(long)ti->cqr);
+#endif
+ continue; // one retry lost 'cause doIO failed
+ }
+ return;
+ }
+ tapeblock_end_request (ti); // check state, inform user, free mem, dev=idl
+ }
+ if (ti->cqr!=NULL) BUG(); // tape should be idle now, request should be freed!
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ return;
+ }
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+ if (list_empty (&ti->request_queue.queue_head)) {
+#else
+ if (ti->request_queue==NULL) {
+#endif
+ // nothing more to do or device has dissapeared;)
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:Qempty");
+#endif
+ tapestate_set(ti,TS_IDLE);
+ return;
+ }
+ // queue is not empty, fetch a request and start IO!
+ req=ti->current_request=tape_next_request(&ti->request_queue);
+ if (req==NULL) {
+ BUG(); // Yo. The queue was not reported empy, but no request found. This is _bad_.
+ }
+ if (req->cmd!=READ) { // we only support reading
+ tapestate_set(ti,TS_FAILED);
+ tapeblock_end_request (ti); // check state, inform user, free mem, dev=idl
+ tapestate_set(ti,TS_BLOCK_INIT);
+ schedule_tapeblock_exec_IO(ti);
+ return;
+ }
+ ti->cqr=ti->discipline->bread(req,ti,tapeblock_major); //build channel program from request
+ if (!ti->cqr) {
+ // ccw generation failed. we try again later.
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"b:cqrNULL");
+#endif
+ schedule_tapeblock_exec_IO(ti);
+ ti->current_request=NULL;
+ return;
+ }
+ ti->blk_retries = TAPEBLOCK_RETRIES;
+ rc= do_IO (ti->devinfo.irq, ti->cqr->cpaddr,
+ (unsigned long) ti->cqr, 0x00, ti->cqr->options);
+ if (rc) {
+ // okay. ssch failed. we try later.
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"b:doIOfail");
+#endif
+ ti->discipline->free_bread(ti->cqr,ti);
+ ti->cqr=NULL;
+ ti->current_request=NULL;
+ schedule_tapeblock_exec_IO(ti);
+ return;
+ }
+ // our request is in IO. we remove it from the queue and exit
+ tape_dequeue_request (&ti->request_queue,req);
+}
+
+static void
+do_tape_request (request_queue_t * queue) {
+ tape_info_t* ti;
+ long lockflags;
+ for (ti=first_tape_info;
+ ((ti!=NULL) && ((&ti->request_queue)!=queue));
+ ti=ti->next);
+ if (ti==NULL) BUG();
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get(ti)!=TS_IDLE) {
+ s390irq_spin_unlock_irqrestore(ti->devinfo.irq,lockflags);
+ return;
+ }
+ if (tapestate_get(ti)!=TS_IDLE) BUG();
+ tapestate_set(ti,TS_BLOCK_INIT);
+ tapeblock_exec_IO(ti);
+ s390irq_spin_unlock_irqrestore(ti->devinfo.irq,lockflags);
+}
+
+static void
+run_tapeblock_exec_IO (tape_info_t* ti) {
+ long flags_390irq,flags_ior;
+ spin_lock_irqsave (&io_request_lock, flags_ior);
+ s390irq_spin_lock_irqsave(ti->devinfo.irq,flags_390irq);
+ atomic_set(&ti->bh_scheduled,0);
+ tapeblock_exec_IO(ti);
+ s390irq_spin_unlock_irqrestore(ti->devinfo.irq,flags_390irq);
+ spin_unlock_irqrestore (&io_request_lock, flags_ior);
+}
+
+void
+schedule_tapeblock_exec_IO (tape_info_t *ti)
+{
+ /* Protect against rescheduling, when already running */
+ if (atomic_compare_and_swap(0,1,&ti->bh_scheduled)) {
+ return;
+ }
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+ INIT_LIST_HEAD(&ti->bh_tq.list);
+#endif
+ ti->bh_tq.sync = 0;
+ ti->bh_tq.routine = (void *) (void *) run_tapeblock_exec_IO;
+ ti->bh_tq.data = ti;
+
+ queue_task (&ti->bh_tq, &tq_immediate);
+ mark_bh (IMMEDIATE_BH);
+ return;
+}
+
+/* wrappers around do_tape_request for different kernel versions */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,98))
+static void tape_request_fn (void) {
+ tape_info_t* ti=first_tape_info;
+ while (ti!=NULL) {
+ do_tape_request(&ti->request_queue);
+ ti=ti->next;
+ }
+}
+#else
+static void tape_request_fn (request_queue_t* queue) {
+ do_tape_request(queue);
+}
+#endif
+
+static request_queue_t* tapeblock_getqueue (kdev_t kdev) {
+ tape_info_t* ti=first_tape_info;
+ while ((ti!=NULL) && (MINOR(kdev)!=ti->blk_minor))
+ ti=ti->next;
+ if (ti!=NULL) return &ti->request_queue;
+ return NULL;
+}
+
+int tapeblock_mediumdetect(tape_info_t* ti) {
+ ccw_req_t* cqr;
+ int losize=1,hisize=1,rc;
+ long lockflags;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"b:medDet");
+#endif
+ PRINT_WARN("Detecting media size. This will take _long_, so get yourself a coffee...\n");
+ while (1) { //is interruped by break
+ hisize=hisize << 1; // try twice the size tested before
+ cqr=ti->discipline->mtseek (ti, hisize);
+ if (cqr == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:ccwg fail");
+#endif
+ return -ENOSPC;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ if (rc) return -EIO;
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ tape_free_request (cqr);
+ if (ti->kernbuf) {
+ kfree (ti->kernbuf);
+ ti->kernbuf=NULL;
+ }
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ break;
+ }
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags);
+ return -ENODEV;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ losize=hisize;
+ }
+ cqr = ti->discipline->mtrew (ti, 1);
+ if (cqr == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:ccwg fail");
+#endif
+ return -ENOSPC;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ tape_free_request (cqr);
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags);
+ return -ENODEV;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ while (losize!=hisize) {
+ cqr=ti->discipline->mtseek (ti, (hisize+losize)/2+1);
+ if (cqr == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:ccwg fail");
+#endif
+ return -ENOSPC;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ if (rc) return -EIO;
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ tape_free_request (cqr);
+ if (ti->kernbuf) {
+ kfree (ti->kernbuf);
+ ti->kernbuf=NULL;
+ }
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags);
+ return -ENODEV;
+ }
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ hisize=(hisize+losize)/2;
+ cqr = ti->discipline->mtrew (ti, 1);
+ if (cqr == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"b:ccwg fail");
+#endif
+ return -ENOSPC;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ tape_free_request (cqr);
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ continue;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ losize=(hisize+losize)/2+1;
+ }
+ blk_size[tapeblock_major][ti->blk_minor]=(losize)*(blksize_size[tapeblock_major][ti->blk_minor]/1024);
+ return 0;
+}
--- /dev/null
+
+/***************************************************************************
+ *
+ * drivers/s390/char/tapechar.h
+ * character device frontend for tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ *
+ ****************************************************************************
+ */
+
+#ifndef TAPEBLOCK_H
+#define TAPEBLOCK_H
+#include <linux/config.h>
+#define PARTN_BITS 0
+
+#define TAPEBLOCK_READAHEAD 30
+#define TAPEBLOCK_MAJOR 0
+
+#define TAPEBLOCK_DEFAULTMODE 0060644
+
+int tapeblock_open(struct inode *, struct file *);
+int tapeblock_release(struct inode *, struct file *);
+void tapeblock_setup(tape_info_t* ti);
+void schedule_tapeblock_exec_IO (tape_info_t *ti);
+int tapeblock_mediumdetect(tape_info_t* ti);
+#ifdef CONFIG_DEVFS_FS
+void tapeblock_mkdevfstree (tape_info_t* ti);
+#endif
+int tapeblock_init (void);
+void tapeblock_uninit (void);
+#endif
--- /dev/null
+
+/***************************************************************************
+ *
+ * drivers/s390/char/tapechar.c
+ * character device frontend for tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ *
+ ****************************************************************************
+ */
+
+#include "tapedefs.h"
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <asm/ccwcache.h> /* CCW allocations */
+#include <asm/s390dyn.h>
+#include <asm/debug.h>
+#include <linux/mtio.h>
+#include <asm/uaccess.h>
+#include <linux/compatmac.h>
+#ifdef MODULE
+#define __NO_VERSION__
+#include <linux/module.h>
+#endif
+#include "tape.h"
+#include "tapechar.h"
+
+#define PRINTK_HEADER "TCHAR:"
+
+/*
+ * file operation structure for tape devices
+ */
+static struct file_operations tape_fops =
+{
+ // owner : THIS_MODULE,
+ llseek:NULL, /* lseek - default */
+ read:tape_read, /* read */
+ write:tape_write, /* write */
+ readdir:NULL, /* readdir - bad */
+ poll:NULL, /* poll */
+ ioctl:tape_ioctl, /* ioctl */
+ mmap:NULL, /* mmap */
+ open:tape_open, /* open */
+ flush:NULL, /* flush */
+ release:tape_release, /* release */
+ fsync:NULL, /* fsync */
+ fasync:NULL, /* fasync */
+ lock:NULL,
+};
+
+int tape_major = TAPE_MAJOR;
+
+#ifdef CONFIG_DEVFS_FS
+void
+tapechar_mkdevfstree (tape_info_t* ti) {
+ ti->devfs_char_dir=devfs_mk_dir (ti->devfs_dir, "char", ti);
+ ti->devfs_nonrewinding=devfs_register(ti->devfs_char_dir, "nonrewinding",
+ DEVFS_FL_DEFAULT,tape_major,
+ ti->nor_minor, TAPECHAR_DEFAULTMODE,
+ &tape_fops, ti);
+ ti->devfs_rewinding=devfs_register(ti->devfs_char_dir, "rewinding",
+ DEVFS_FL_DEFAULT, tape_major, ti->rew_minor,
+ TAPECHAR_DEFAULTMODE, &tape_fops, ti);
+}
+
+void
+tapechar_rmdevfstree (tape_info_t* ti) {
+ devfs_unregister(ti->devfs_nonrewinding);
+ devfs_unregister(ti->devfs_rewinding);
+ devfs_unregister(ti->devfs_char_dir);
+}
+#endif
+
+void
+tapechar_setup (tape_info_t * ti)
+{
+#ifdef CONFIG_DEVFS_FS
+ tapechar_mkdevfstree(ti);
+#endif
+}
+
+void
+tapechar_init (void)
+{
+ int result;
+ tape_frontend_t *charfront,*temp;
+ tape_info_t* ti;
+
+ tape_init();
+
+ /* Register the tape major number to the kernel */
+#ifdef CONFIG_DEVFS_FS
+ result = devfs_register_chrdev (tape_major, "tape", &tape_fops);
+#else
+ result = register_chrdev (tape_major, "tape", &tape_fops);
+#endif
+
+ if (result < 0) {
+ PRINT_WARN (KERN_ERR "tape: can't get major %d\n", tape_major);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"c:initfail");
+ debug_text_event (tape_debug_area,3,"regchrfail");
+#endif /* TAPE_DEBUG */
+ panic ("no major number available for tape char device");
+ }
+ if (tape_major == 0)
+ tape_major = result; /* accept dynamic major number */
+ PRINT_WARN (KERN_ERR " tape gets major %d for character device\n", result);
+ charfront = kmalloc (sizeof (tape_frontend_t), GFP_KERNEL);
+ if (charfront == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"c:initfail");
+ debug_text_event (tape_debug_area,3,"no mem");
+#endif /* TAPE_DEBUG */
+ panic ("no major number available for tape char device");
+ }
+ charfront->device_setup = tapechar_setup;
+#ifdef CONFIG_DEVFS_FS
+ charfront->mkdevfstree = tapechar_mkdevfstree;
+ charfront->rmdevfstree = tapechar_rmdevfstree;
+#endif
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"c:init ok");
+#endif /* TAPE_DEBUG */
+ charfront->next=NULL;
+ if (first_frontend==NULL) {
+ first_frontend=charfront;
+ } else {
+ temp=first_frontend;
+ while (temp->next!=NULL)
+ temp=temp->next;
+ temp->next=charfront;
+ }
+ ti=first_tape_info;
+ while (ti!=NULL) {
+ tapechar_setup(ti);
+ ti=ti->next;
+ }
+}
+
+void
+tapechar_uninit (void)
+{
+ unregister_chrdev (tape_major, "tape");
+}
+
+/*
+ * Tape device read function
+ */
+ssize_t
+tape_read (struct file *filp, char *data, size_t count, loff_t * ppos)
+{
+ long lockflags;
+ tape_info_t *ti;
+ size_t block_size;
+ ccw_req_t *cqr;
+ int rc;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:read");
+#endif /* TAPE_DEBUG */
+ ti = first_tape_info;
+ while ((ti != NULL) && (ti->rew_filp != filp) && (ti->nor_filp != filp))
+ ti = (tape_info_t *) ti->next;
+ if (ti == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:nodev");
+#endif /* TAPE_DEBUG */
+ return -ENODEV;
+ }
+ if (ppos != &filp->f_pos) {
+ /* "A request was outside the capabilities of the device." */
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:ppos wrong");
+#endif /* TAPE_DEBUG */
+ return -EOVERFLOW; /* errno=75 Value too large for def. data type */
+ }
+ if (ti->block_size == 0) {
+ block_size = count;
+ } else {
+ block_size = ti->block_size;
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:nbytes:");
+ debug_int_event (tape_debug_area,6,block_size);
+#endif
+ cqr = ti->discipline->read_block (data, block_size, ti);
+ if (!cqr) {
+ return -ENOBUFS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ if (rc) {
+ tapestate_set(ti,TS_IDLE);
+ kfree (cqr);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return rc;
+ }
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ ti->discipline->free_read_block (cqr, ti);
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return ti->rc;
+ }
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags);
+ return -ENODEV;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:rbytes:");
+ debug_int_event (tape_debug_area,6,block_size - ti->devstat.rescnt);
+#endif /* TAPE_DEBUG */
+ filp->f_pos += block_size - ti->devstat.rescnt;
+ return block_size - ti->devstat.rescnt;
+}
+
+/*
+ * Tape device write function
+ */
+ssize_t
+tape_write (struct file *filp, const char *data, size_t count, loff_t * ppos)
+{
+ long lockflags;
+ tape_info_t *ti;
+ size_t block_size;
+ ccw_req_t *cqr;
+ int nblocks, i, rc;
+ size_t written = 0;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:write");
+#endif
+ ti = first_tape_info;
+ while ((ti != NULL) && (ti->nor_filp != filp) && (ti->rew_filp != filp))
+ ti = (tape_info_t *) ti->next;
+ if (ti == NULL)
+ return -ENODEV;
+ if (ppos != &filp->f_pos) {
+ /* "A request was outside the capabilities of the device." */
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:ppos wrong");
+#endif
+ return -EOVERFLOW; /* errno=75 Value too large for def. data type */
+ }
+ if ((ti->block_size != 0) && (count % ti->block_size != 0))
+ return -EIO;
+ if (ti->block_size == 0) {
+ block_size = count;
+ nblocks = 1;
+ } else {
+ block_size = ti->block_size;
+ nblocks = count / (ti->block_size);
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:nbytes:");
+ debug_int_event (tape_debug_area,6,block_size);
+ debug_text_event (tape_debug_area,6,"c:nblocks:");
+ debug_int_event (tape_debug_area,6,nblocks);
+#endif
+ for (i = 0; i < nblocks; i++) {
+ cqr = ti->discipline->write_block (data + i * block_size, block_size, ti);
+ if (!cqr) {
+ return -ENOBUFS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ ti->discipline->free_write_block (cqr, ti);
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ if ((ti->rc==-ENOSPC) && (i!=0))
+ return i*block_size;
+ return ti->rc;
+ }
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags);
+ return -ENODEV;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:wbytes:");
+ debug_int_event (tape_debug_area,6,block_size - ti->devstat.rescnt);
+#endif
+ filp->f_pos += block_size - ti->devstat.rescnt;
+ written += block_size - ti->devstat.rescnt;
+ if (ti->devstat.rescnt > 0)
+ return written;
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:wtotal:");
+ debug_int_event (tape_debug_area,6,written);
+#endif
+ return written;
+}
+
+static int
+tape_mtioctop (struct file *filp, short mt_op, int mt_count)
+{
+ tape_info_t *ti;
+ ccw_req_t *cqr = NULL;
+ int rc;
+ long lockflags;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:mtio");
+ debug_text_event (tape_debug_area,6,"c:ioop:");
+ debug_int_event (tape_debug_area,6,mt_op);
+ debug_text_event (tape_debug_area,6,"c:arg:");
+ debug_int_event (tape_debug_area,6,mt_count);
+#endif
+ ti = first_tape_info;
+ while ((ti != NULL) && (ti->rew_filp != filp) && (ti->nor_filp != filp))
+ ti = (tape_info_t *) ti->next;
+ if (ti == NULL)
+ return -ENODEV;
+ switch (mt_op) {
+ case MTREW: // rewind
+
+ cqr = ti->discipline->mtrew (ti, mt_count);
+ break;
+ case MTOFFL: // put drive offline
+
+ cqr = ti->discipline->mtoffl (ti, mt_count);
+ break;
+ case MTUNLOAD: // unload the tape
+
+ cqr = ti->discipline->mtunload (ti, mt_count);
+ break;
+ case MTWEOF: // write tapemark
+
+ cqr = ti->discipline->mtweof (ti, mt_count);
+ break;
+ case MTFSF: // forward space file
+
+ cqr = ti->discipline->mtfsf (ti, mt_count);
+ break;
+ case MTBSF: // backward space file
+
+ cqr = ti->discipline->mtbsf (ti, mt_count);
+ break;
+ case MTFSFM: // forward space file, stop at BOT side
+
+ cqr = ti->discipline->mtfsfm (ti, mt_count);
+ break;
+ case MTBSFM: // backward space file, stop at BOT side
+
+ cqr = ti->discipline->mtbsfm (ti, mt_count);
+ break;
+ case MTFSR: // forward space file
+
+ cqr = ti->discipline->mtfsr (ti, mt_count);
+ break;
+ case MTBSR: // backward space file
+
+ cqr = ti->discipline->mtbsr (ti, mt_count);
+ break;
+ case MTNOP:
+ cqr = ti->discipline->mtnop (ti, mt_count);
+ break;
+ case MTEOM: // postion at the end of portion
+
+ case MTRETEN: // retension the tape
+
+ cqr = ti->discipline->mteom (ti, mt_count);
+ break;
+ case MTERASE:
+ cqr = ti->discipline->mterase (ti, mt_count);
+ break;
+ case MTSETDENSITY:
+ cqr = ti->discipline->mtsetdensity (ti, mt_count);
+ break;
+ case MTSEEK:
+ cqr = ti->discipline->mtseek (ti, mt_count);
+ break;
+ case MTSETDRVBUFFER:
+ cqr = ti->discipline->mtsetdrvbuffer (ti, mt_count);
+ break;
+ case MTLOCK:
+ cqr = ti->discipline->mtsetdrvbuffer (ti, mt_count);
+ break;
+ case MTUNLOCK:
+ cqr = ti->discipline->mtsetdrvbuffer (ti, mt_count);
+ break;
+ case MTLOAD:
+ cqr = ti->discipline->mtload (ti, mt_count);
+ if (cqr!=NULL) break; // if backend driver has an load function ->use it
+ // if no medium is in, wait until it gets inserted
+ if (ti->medium_is_unloaded) {
+ wait_event_interruptible (ti->wq,ti->medium_is_unloaded==0);
+ }
+ return 0;
+ case MTCOMPRESSION:
+ cqr = ti->discipline->mtcompression (ti, mt_count);
+ break;
+ case MTSETPART:
+ cqr = ti->discipline->mtsetpart (ti, mt_count);
+ break;
+ case MTMKPART:
+ cqr = ti->discipline->mtmkpart (ti, mt_count);
+ break;
+ case MTTELL: // return number of block relative to current file
+
+ cqr = ti->discipline->mttell (ti, mt_count);
+ break;
+ case MTSETBLK:
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->block_size = mt_count;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:setblk:");
+ debug_int_event (tape_debug_area,6,mt_count);
+#endif
+ return 0;
+ case MTRESET:
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->kernbuf = ti->userbuf = NULL;
+ tapestate_set (ti, TS_IDLE);
+ ti->block_size = 0;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:devreset:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif
+ return 0;
+ default:
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:inv.mtio");
+#endif
+ return -EINVAL;
+ }
+ if (cqr == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:ccwg fail");
+#endif
+ return -ENOSPC;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ if (ti->kernbuf != NULL) {
+ kfree (ti->kernbuf);
+ ti->kernbuf = NULL;
+ }
+ tape_free_request (cqr);
+ // if medium was unloaded, update the corresponding variable.
+ switch (mt_op) {
+ case MTOFFL:
+ case MTUNLOAD:
+ ti->medium_is_unloaded=1;
+ }
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (((mt_op == MTEOM) || (mt_op == MTRETEN)) && (tapestate_get (ti) == TS_FAILED))
+ tapestate_set (ti, TS_DONE);
+ if (tapestate_get (ti) == TS_FAILED) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return ti->rc;
+ }
+ if (tapestate_get (ti) == TS_NOT_OPER) {
+ ti->blk_minor=ti->rew_minor=ti->nor_minor=-1;
+ ti->devinfo.irq=-1;
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq,lockflags);
+ return -ENODEV;
+ }
+ if (tapestate_get (ti) != TS_DONE) {
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ return -EIO;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ switch (mt_op) {
+ case MTRETEN: //need to rewind the tape after moving to eom
+
+ return tape_mtioctop (filp, MTREW, 1);
+ case MTFSFM: //need to skip back over the filemark
+
+ return tape_mtioctop (filp, MTBSFM, 1);
+ case MTBSF: //need to skip forward over the filemark
+
+ return tape_mtioctop (filp, MTFSF, 1);
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:mtio done");
+#endif
+ return 0;
+}
+
+/*
+ * Tape device io controls.
+ */
+int
+tape_ioctl (struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ long lockflags;
+ tape_info_t *ti;
+ ccw_req_t *cqr;
+ struct mtop op; /* structure for MTIOCTOP */
+ struct mtpos pos; /* structure for MTIOCPOS */
+ struct mtget get;
+
+ int rc;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:ioct");
+#endif
+ ti = first_tape_info;
+ while ((ti != NULL) &&
+ (ti->rew_minor != MINOR (inode->i_rdev)) &&
+ (ti->nor_minor != MINOR (inode->i_rdev)))
+ ti = (tape_info_t *) ti->next;
+ if (ti == NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:nodev");
+#endif
+ return -ENODEV;
+ }
+ // check for discipline ioctl overloading
+ if ((rc = ti->discipline->discipline_ioctl_overload (inode, filp, cmd, arg))
+ != -EINVAL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:ioverloa");
+#endif
+ return rc;
+ }
+
+ switch (cmd) {
+ case MTIOCTOP: /* tape op command */
+ if (copy_from_user (&op, (char *) arg, sizeof (struct mtop))) {
+ return -EFAULT;
+ }
+ return (tape_mtioctop (filp, op.mt_op, op.mt_count));
+ case MTIOCPOS: /* query tape position */
+ cqr = ti->discipline->mttell (ti, 0);
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ pos.mt_blkno = ti->rc;
+ ti->cqr = NULL;
+ if (ti->kernbuf != NULL) {
+ kfree (ti->kernbuf);
+ ti->kernbuf = NULL;
+ }
+ tape_free_request (cqr);
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ if (copy_to_user ((char *) arg, &pos, sizeof (struct mtpos)))
+ return -EFAULT;
+ return 0;
+ case MTIOCGET:
+ get.mt_erreg = ti->rc;
+ cqr = ti->discipline->mttell (ti, 0);
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event_interruptible (ti->wq,ti->wanna_wakeup);
+ get.mt_blkno = ti->rc;
+ get.mt_fileno = 0;
+ get.mt_type = MT_ISUNKNOWN;
+ get.mt_resid = ti->devstat.rescnt;
+ get.mt_dsreg = ti->devstat.ii.sense.data[3];
+ get.mt_gstat = 0;
+ if (ti->devstat.ii.sense.data[1] & 0x08)
+ get.mt_gstat &= GMT_BOT (1); // BOT
+
+ if (ti->devstat.ii.sense.data[1] & 0x02)
+ get.mt_gstat &= GMT_WR_PROT (1); // write protected
+
+ if (ti->devstat.ii.sense.data[1] & 0x40)
+ get.mt_gstat &= GMT_ONLINE (1); //drive online
+
+ ti->cqr = NULL;
+ if (ti->kernbuf != NULL) {
+ kfree (ti->kernbuf);
+ ti->kernbuf = NULL;
+ }
+ tape_free_request (cqr);
+ if (signal_pending (current)) {
+ tapestate_set (ti, TS_IDLE);
+ return -ERESTARTSYS;
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ if (copy_to_user ((char *) arg, &get, sizeof (struct mtget)))
+ return -EFAULT;
+ return 0;
+ default:
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,3,"c:ioct inv");
+#endif
+ return -EINVAL;
+ }
+}
+
+/*
+ * Tape device open function.
+ */
+int
+tape_open (struct inode *inode, struct file *filp)
+{
+ tape_info_t *ti;
+ kdev_t dev;
+ long lockflags;
+
+ inode = filp->f_dentry->d_inode;
+ ti = first_tape_info;
+ while ((ti != NULL) &&
+ (ti->rew_minor != MINOR (inode->i_rdev)) &&
+ (ti->nor_minor != MINOR (inode->i_rdev)))
+ ti = (tape_info_t *) ti->next;
+ if (ti == NULL)
+ return -ENODEV;
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:open:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (tapestate_get (ti) != TS_UNUSED) {
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:dbusy");
+#endif
+ return -EBUSY;
+ }
+ tapestate_set (ti, TS_IDLE);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+
+ dev = MKDEV (tape_major, MINOR (inode->i_rdev)); /* Get the device */
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ if (ti->rew_minor == MINOR (inode->i_rdev))
+ ti->rew_filp = filp; /* save for later reference */
+ else
+ ti->nor_filp = filp;
+ filp->private_data = ti; /* save the dev.info for later reference */
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif /* MODULE */
+ return 0;
+}
+
+/*
+ * Tape device release function.
+ */
+int
+tape_release (struct inode *inode, struct file *filp)
+{
+ long lockflags;
+ tape_info_t *ti,*lastti;
+ ccw_req_t *cqr = NULL;
+ int rc;
+
+ ti = first_tape_info;
+ while ((ti != NULL) && (ti->rew_minor != MINOR (inode->i_rdev)) && (ti->nor_minor != MINOR (inode->i_rdev)))
+ ti = (tape_info_t *) ti->next;
+ if ((ti != NULL) && (tapestate_get (ti) == TS_NOT_OPER)) {
+ if (ti==first_tape_info) {
+ first_tape_info=ti->next;
+ } else {
+ lastti=first_tape_info;
+ while (lastti->next!=ti) lastti=lastti->next;
+ lastti->next=ti->next;
+ }
+ kfree(ti);
+ return 0;
+ }
+ if ((ti == NULL) || (tapestate_get (ti) != TS_IDLE)) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:notidle!");
+#endif
+ return -ENXIO; /* error in tape_release */
+ }
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:release:");
+ debug_int_event (tape_debug_area,6,ti->blk_minor);
+#endif
+ if (ti->rew_minor == MINOR (inode->i_rdev)) {
+ cqr = ti->discipline->mtrew (ti, 1);
+ if (cqr != NULL) {
+#ifdef TAPE_DEBUG
+ debug_text_event (tape_debug_area,6,"c:rewrelea");
+#endif
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ tapestate_set (ti, TS_REW_RELEASE_INIT);
+ ti->cqr = cqr;
+ ti->wanna_wakeup=0;
+ rc = do_IO (ti->devinfo.irq, cqr->cpaddr, (unsigned long) cqr, 0x00, cqr->options);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+ wait_event (ti->wq,ti->wanna_wakeup);
+ ti->cqr = NULL;
+ tape_free_request (cqr);
+ }
+ }
+ s390irq_spin_lock_irqsave (ti->devinfo.irq, lockflags);
+ tapestate_set (ti, TS_UNUSED);
+ s390irq_spin_unlock_irqrestore (ti->devinfo.irq, lockflags);
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif /* MODULE */
+ return 0;
+}
--- /dev/null
+
+/***************************************************************************
+ *
+ * drivers/s390/char/tapechar.h
+ * character device frontend for tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ *
+ ****************************************************************************
+ */
+
+#ifndef TAPECHAR_H
+#define TAPECHAR_H
+#include <linux/config.h>
+#define TAPECHAR_DEFAULTMODE 0020644
+#define TAPE_MAJOR 0 /* get dynamic major since no major officialy defined for tape */
+/*
+ * Prototypes for tape_fops
+ */
+ssize_t tape_read(struct file *, char *, size_t, loff_t *);
+ssize_t tape_write(struct file *, const char *, size_t, loff_t *);
+int tape_ioctl(struct inode *,struct file *,unsigned int,unsigned long);
+int tape_open (struct inode *,struct file *);
+int tape_release (struct inode *,struct file *);
+#ifdef CONFIG_DEVFS_FS
+void tapechar_mkdevfstree (tape_info_t* ti);
+#endif
+void tapechar_init (void);
+void tapechar_uninit (void);
+#endif /* TAPECHAR_H */
--- /dev/null
+/***********************************************************************
+ * drivers/s390/char/tapedefs.h
+ * tape device driver for S/390 and zSeries tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ *
+ ***********************************************************************
+ */
+
+/* Kernel Version Compatibility section */
+#include <linux/version.h>
+#include <linux/blkdev.h>
+#include <linux/blk.h>
+#include <asm/irq.h>
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,2,17))
+#define TAPE_DEBUG // use s390 debug feature
+#else
+#undef TAPE_DEBUG // debug feature not supported by our 2.2.16 code
+static inline void set_normalized_cda ( ccw1_t * cp, unsigned long address ) {
+ cp -> cda = address;
+}
+static inline void clear_normalized_cda ( ccw1_t * ccw ) {
+ ccw -> cda = 0;
+}
+#define BUG() PRINT_FATAL("tape390: CRITICAL INTERNAL ERROR OCCURED. REPORT THIS BACK TO LINUX390@DE.IBM.COM\n")
+#endif
+#define CONFIG_S390_TAPE_DYNAMIC // allow devices to be attached or detached on the fly
+#define TAPEBLOCK_RETRIES 20 // number of retries, when a block-dev request fails.
+
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,98))
+#define INIT_BLK_DEV(d_major,d_request_fn,d_queue_fn,d_current) \
+do { \
+ blk_dev[d_major].queue = d_queue_fn; \
+} while(0)
+static inline struct request *
+tape_next_request( request_queue_t *queue )
+{
+ return blkdev_entry_next_request(&queue->queue_head);
+}
+static inline void
+tape_dequeue_request( request_queue_t * q, struct request *req )
+{
+ blkdev_dequeue_request (req);
+}
+#else
+#define s390_dev_info_t dev_info_t
+typedef struct request *request_queue_t;
+#ifndef init_waitqueue_head
+#define init_waitqueue_head(x) do { *x = NULL; } while(0)
+#endif
+#define blk_init_queue(x,y) do {} while(0)
+#define blk_queue_headactive(x,y) do {} while(0)
+#define INIT_BLK_DEV(d_major,d_request_fn,d_queue_fn,d_current) \
+do { \
+ blk_dev[d_major].request_fn = d_request_fn; \
+ blk_dev[d_major].queue = d_queue_fn; \
+ blk_dev[d_major].current_request = d_current; \
+} while(0)
+static inline struct request *
+tape_next_request( request_queue_t *queue )
+{
+ return *queue;
+}
+static inline void
+tape_dequeue_request( request_queue_t * q, struct request *req )
+{
+ *q = req->next;
+ req->next = NULL;
+}
+#endif
#include <asm/atomic.h>
#include "iucv.h"
#include <asm/io.h>
+#include <asm/irq.h>
#include <asm/s390_ext.h>
#include <asm/spinlock.h>
#include <asm/ebcdic.h>
inline void
top_half_interrupt (struct pt_regs *regs, __u16 code)
{
+ int cpu = smp_processor_id();
iucv_packet *pkt;
+
+ irq_enter(cpu, 0x4000);
+
pkt = (iucv_packet *) kmalloc
(sizeof (iucv_packet), GFP_ATOMIC);
if (pkt == NULL) {
printk (KERN_DEBUG "out of memory\n");
+ irq_exit(cpu, 0x4000);
return;
}
memcpy (pkt->data, iucv_external_int_buffer, 40);
queue_task (&short_task, &tq_immediate);
mark_bh (IMMEDIATE_BH);
}
+ irq_exit(cpu, 0x4000);
return;
}
/*
* linux/drivers/sbus/char/aurora.c -- Aurora multiport driver
*
- * Copyright (c) 1999 by Oliver Aldulea (oli@bv.ro)
+ * Copyright (c) 1999 by Oliver Aldulea (oli at bv.ro)
*
* This code is based on the RISCom/8 multiport serial driver written
* by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial
# Multipart objects.
list-multi := usbcore.o
-usbcore-objs := usb.o usb-debug.o usb-core.o hub.o
+usbcore-objs := usb.o usb-debug.o hub.o
# Optional parts of multipart objects.
/*
- * acm.c Version 0.16
+ * acm.c Version 0.18
*
* Copyright (c) 1999 Armin Fuerst <fuerst@in.tum.de>
* Copyright (c) 1999 Pavel Machek <pavel@suse.cz>
* v0.14 - sized down struct acm
* v0.15 - fixed flow control again - characters could be lost
* v0.16 - added code for modems with swapped data and control interfaces
+ * v0.17 - added new style probing
+ * v0.18 - fixed new style probing for devices with more configurations
*/
/*
#include <linux/init.h>
#include <linux/malloc.h>
#include <linux/fcntl.h>
+#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
-#include <linux/tty.h>
#include <linux/module.h>
-//#define DEBUG
+#undef DEBUG
#include <linux/usb.h>
#include <linux/devfs_fs_kernel.h>
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.18"
+#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik"
+#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
+
/*
* CMSPAR, some architectures can't have space and mark parity.
*/
static struct usb_driver acm_driver;
static struct tty_driver acm_tty_driver;
-static struct acm *acm_table[ACM_TTY_MINORS] = { NULL, /* .... */ };
+static struct acm *acm_table[ACM_TTY_MINORS];
#define ACM_READY(acm) (acm && acm->dev && acm->used)
dbg("nonzero read bulk status received: %d", urb->status);
if (!urb->status & !acm->throttle) {
- for (i = 0; i < urb->actual_length && !acm->throttle; i++)
+ for (i = 0; i < urb->actual_length && !acm->throttle; i++) {
+ /* if we insert more than TTY_FLIPBUF_SIZE characters,
+ * we drop them. */
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ tty_flip_buffer_push(tty);
+ }
tty_insert_flip_char(tty, data[i], 0);
+ }
tty_flip_buffer_push(tty);
}
}
urb->actual_length = 0;
+ urb->dev = acm->dev;
if (usb_submit_urb(urb))
dbg("failed resubmitting read urb");
if (acm->used++) return 0;
+ acm->ctrlurb.dev = acm->dev;
if (usb_submit_urb(&acm->ctrlurb))
dbg("usb_submit_urb(ctrl irq) failed");
+ acm->readurb.dev = acm->dev;
if (usb_submit_urb(&acm->readurb))
dbg("usb_submit_urb(read bulk) failed");
acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS);
+ /* force low_latency on so that our tty_push actually forces the data through,
+ otherwise it is scheduled, and with high data rates data can get lost. */
+ tty->low_latency = 1;
+
return 0;
}
memcpy(acm->writeurb.transfer_buffer, buf, count);
acm->writeurb.transfer_buffer_length = count;
+ acm->writeurb.dev = acm->dev;
if (usb_submit_urb(&acm->writeurb))
dbg("usb_submit_urb(write bulk) failed");
ifcom = cfacm->interface[0].altsetting + 0;
ifdata = cfacm->interface[1].altsetting + 0;
- if (ifdata->bInterfaceClass != 10 || ifdata->bNumEndpoints != 2) {
+ if (ifdata->bInterfaceClass != 10 || ifdata->bNumEndpoints < 2) {
ifcom = cfacm->interface[1].altsetting + 0;
ifdata = cfacm->interface[0].altsetting + 0;
if (ifdata->bInterfaceClass != 10 || ifdata->bNumEndpoints < 2)
FILL_BULK_URB(&acm->writeurb, dev, usb_sndbulkpipe(dev, epwrite->bEndpointAddress),
buf += readsize, acm->writesize, acm_write_bulk, acm);
-
+
printk(KERN_INFO "ttyACM%d: USB ACM device\n", minor);
acm_set_control(acm, acm->ctrlout);
return -1;
}
+ info(DRIVER_VERSION ":" DRIVER_DESC);
+
return 0;
}
module_init(acm_init);
module_exit(acm_exit);
-MODULE_AUTHOR("Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik");
-MODULE_DESCRIPTION("USB Abstract Control Model driver for USB modems and ISDN adapters");
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+
return -1;
}
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
dbg("dabusb_init: driver registered");
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
{
if (usb_register (&camera_driver) < 0)
return -1;
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
static int khubd_pid = 0; /* PID of khubd */
-static int khubd_running = 0;
+static DECLARE_MUTEX_LOCKED(khubd_exited);
static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size)
{
data, sizeof(struct usb_hub_status), HZ);
}
-/*
- * A irq handler returns non-zero to indicate to
- * the low-level driver that it wants to be re-activated,
- * or zero to say "I'm done".
- */
static void hub_irq(struct urb *urb)
{
struct usb_hub *hub = (struct usb_hub *)urb->context;
unsigned long flags;
+ /* Cause a hub reset after 10 consecutive errors */
if (urb->status) {
- if (urb->status != -ENOENT)
- dbg("nonzero status in irq %d", urb->status);
+ if (urb->status == -ENOENT)
+ return;
- return;
+ dbg("nonzero status in irq %d", urb->status);
+
+ if ((++hub->nerrors < 10) || hub->error)
+ return;
+
+ hub->error = urb->status;
}
+ hub->nerrors = 0;
+
/* Something happened, let khubd figure it out */
- if (waitqueue_active(&khubd_wait)) {
- /* Add the hub to the event queue */
- spin_lock_irqsave(&hub_event_lock, flags);
- if (hub->event_list.next == &hub->event_list) {
- list_add(&hub->event_list, &hub_event_list);
- /* Wake up khubd */
- wake_up(&khubd_wait);
- }
- spin_unlock_irqrestore(&hub_event_lock, flags);
+ spin_lock_irqsave(&hub_event_lock, flags);
+ if (list_empty(&hub->event_list)) {
+ list_add(&hub->event_list, &hub_event_list);
+ wake_up(&khubd_wait);
}
+ spin_unlock_irqrestore(&hub_event_lock, flags);
}
static void usb_hub_power_on(struct usb_hub *hub)
dbg("enabling power on all ports");
for (i = 0; i < hub->nports; i++)
usb_set_port_feature(hub->dev, i + 1, USB_PORT_FEAT_POWER);
+
+ /* Wait for power to be enabled */
+ wait_ms(hub->descriptor->bPwrOn2PwrGood * 2);
}
-static int usb_hub_configure(struct usb_hub *hub)
+static int usb_hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint)
{
struct usb_device *dev = hub->dev;
- unsigned char buffer[HUB_DESCRIPTOR_MAX_SIZE], *bitmap;
- struct usb_hub_descriptor *descriptor;
- struct usb_descriptor_header *header;
- struct usb_hub_status *hubsts;
- int i, ret;
+ struct usb_hub_status hubstatus;
+ char portstr[USB_MAXCHILDREN + 1];
+ unsigned int pipe;
+ int i, maxp, ret;
+
+ hub->descriptor = kmalloc(HUB_DESCRIPTOR_MAX_SIZE, GFP_KERNEL);
+ if (!hub->descriptor) {
+ err("Unable to kmalloc %d bytes for hub descriptor", HUB_DESCRIPTOR_MAX_SIZE);
+ return -1;
+ }
/* Request the entire hub descriptor. */
- header = (struct usb_descriptor_header *)buffer;
- ret = usb_get_hub_descriptor(dev, buffer, sizeof(buffer));
- /* <buffer> is large enough for a hub with 127 ports;
+ ret = usb_get_hub_descriptor(dev, hub->descriptor, HUB_DESCRIPTOR_MAX_SIZE);
+ /* <hub->descriptor> is large enough for a hub with 127 ports;
* the hub can/will return fewer bytes here. */
if (ret < 0) {
err("Unable to get hub descriptor (err = %d)", ret);
+ kfree(hub->descriptor);
return -1;
}
- bitmap = kmalloc(header->bLength, GFP_KERNEL);
- if (!bitmap) {
- err("Unable to kmalloc %d bytes for bitmap", header->bLength);
- return -1;
- }
-
- memcpy (bitmap, buffer, header->bLength);
- descriptor = (struct usb_hub_descriptor *)bitmap;
+ le16_to_cpus(&hub->descriptor->wHubCharacteristics);
- hub->nports = dev->maxchild = descriptor->bNbrPorts;
+ hub->nports = dev->maxchild = hub->descriptor->bNbrPorts;
info("%d port%s detected", hub->nports, (hub->nports == 1) ? "" : "s");
- switch (descriptor->wHubCharacteristics & HUB_CHAR_LPSM) {
+ if (hub->descriptor->wHubCharacteristics & HUB_CHAR_COMPOUND)
+ dbg("part of a compound device");
+ else
+ dbg("standalone hub");
+
+ switch (hub->descriptor->wHubCharacteristics & HUB_CHAR_LPSM) {
case 0x00:
dbg("ganged power switching");
break;
break;
}
- if (descriptor->wHubCharacteristics & HUB_CHAR_COMPOUND)
- dbg("part of a compound device");
- else
- dbg("standalone hub");
-
- switch (descriptor->wHubCharacteristics & HUB_CHAR_OCPM) {
+ switch (hub->descriptor->wHubCharacteristics & HUB_CHAR_OCPM) {
case 0x00:
dbg("global over-current protection");
break;
break;
}
- dbg("power on to power good time: %dms", descriptor->bPwrOn2PwrGood * 2);
- dbg("hub controller current requirement: %dmA", descriptor->bHubContrCurrent);
+ dbg("power on to power good time: %dms", hub->descriptor->bPwrOn2PwrGood * 2);
+ dbg("hub controller current requirement: %dmA", hub->descriptor->bHubContrCurrent);
for (i = 0; i < dev->maxchild; i++)
- dbg("port %d is%s removable", i + 1,
- bitmap[7 + ((i + 1)/8)] & (1 << ((i + 1) % 8))
- ? " not" : "");
+ portstr[i] = hub->descriptor->bitmap[((i + 1) / 8)] & (1 << ((i + 1) % 8)) ? 'F' : 'R';
+ portstr[dev->maxchild] = 0;
- kfree(bitmap);
+ dbg("port removable status: %s", portstr);
- ret = usb_get_hub_status(dev, buffer);
+ ret = usb_get_hub_status(dev, &hubstatus);
if (ret < 0) {
err("Unable to get hub status (err = %d)", ret);
+ kfree(hub->descriptor);
return -1;
}
- hubsts = (struct usb_hub_status *)buffer;
+ le16_to_cpus(&hubstatus.wHubStatus);
+
dbg("local power source is %s",
- (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? "lost (inactive)" : "good");
+ (hubstatus.wHubStatus & HUB_STATUS_LOCAL_POWER) ? "lost (inactive)" : "good");
dbg("%sover-current condition exists",
- (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? "" : "no ");
+ (hubstatus.wHubStatus & HUB_STATUS_OVERCURRENT) ? "" : "no ");
+
+ /* Start the interrupt endpoint */
+ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+ if (maxp > sizeof(hub->buffer))
+ maxp = sizeof(hub->buffer);
+
+ hub->urb = usb_alloc_urb(0);
+ if (!hub->urb) {
+ err("couldn't allocate interrupt urb");
+ kfree(hub->descriptor);
+ return -1;
+ }
+
+ FILL_INT_URB(hub->urb, dev, pipe, hub->buffer, maxp, hub_irq,
+ hub, endpoint->bInterval);
+ ret = usb_submit_urb(hub->urb);
+ if (ret) {
+ err("usb_submit_urb failed (%d)", ret);
+ kfree(hub->descriptor);
+ return -1;
+ }
+
+ /* Wake up khubd */
+ wake_up(&khubd_wait);
usb_hub_power_on(hub);
struct usb_endpoint_descriptor *endpoint;
struct usb_hub *hub;
unsigned long flags;
- unsigned int pipe;
- int maxp, ret;
interface = &dev->actconfig->interface[i].altsetting[0];
/* Some hubs have a subclass of 1, which AFAICT according to the */
/* specs is not defined, but it works */
if ((interface->bInterfaceSubClass != 0) &&
- (interface->bInterfaceSubClass != 1))
+ (interface->bInterfaceSubClass != 1)) {
+ err("invalid subclass (%d) for USB hub device #%d",
+ interface->bInterfaceSubClass, dev->devnum);
return NULL;
+ }
/* Multiple endpoints? What kind of mutant ninja-hub is this? */
- if (interface->bNumEndpoints != 1)
+ if (interface->bNumEndpoints != 1) {
+ err("invalid bNumEndpoints (%d) for USB hub device #%d",
+ interface->bNumEndpoints, dev->devnum);
return NULL;
+ }
endpoint = &interface->endpoint[0];
/* Output endpoint? Curiousier and curiousier.. */
if (!(endpoint->bEndpointAddress & USB_DIR_IN)) {
- err("Device is hub class, but has output endpoint?");
+ err("Device #%d is hub class, but has output endpoint?",
+ dev->devnum);
return NULL;
}
/* If it's not an interrupt endpoint, we'd better punt! */
- if ((endpoint->bmAttributes & 3) != 3) {
- err("Device is hub class, but has endpoint other than interrupt?");
+ if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) {
+ err("Device #%d is hub class, but has endpoint other than interrupt?",
+ dev->devnum);
return NULL;
}
INIT_LIST_HEAD(&hub->event_list);
hub->dev = dev;
+ atomic_set(&hub->refcnt, 1);
/* Record the new hub's existence */
spin_lock_irqsave(&hub_event_lock, flags);
list_add(&hub->hub_list, &hub_list);
spin_unlock_irqrestore(&hub_event_lock, flags);
- if (usb_hub_configure(hub) >= 0) {
- pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
- maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
-
- if (maxp > sizeof(hub->buffer))
- maxp = sizeof(hub->buffer);
-
- hub->urb = usb_alloc_urb(0);
- if (!hub->urb) {
- err("couldn't allocate interrupt urb");
- goto fail;
- }
-
- FILL_INT_URB(hub->urb, dev, pipe, hub->buffer, maxp, hub_irq,
- hub, endpoint->bInterval);
- ret = usb_submit_urb(hub->urb);
- if (ret) {
- err("usb_submit_urb failed (%d)", ret);
- goto fail;
- }
-
- /* Wake up khubd */
- wake_up(&khubd_wait);
- }
+ if (usb_hub_configure(hub, endpoint) >= 0)
+ return hub;
- return hub;
+ err("hub configuration failed for device #%d", dev->devnum);
-fail:
/* free hub, but first clean up its list. */
spin_lock_irqsave(&hub_event_lock, flags);
return NULL;
}
+static void hub_get(struct usb_hub *hub)
+{
+ atomic_inc(&hub->refcnt);
+}
+
+static void hub_put(struct usb_hub *hub)
+{
+ if (atomic_dec_and_test(&hub->refcnt)) {
+ if (hub->descriptor) {
+ kfree(hub->descriptor);
+ hub->descriptor = NULL;
+ }
+
+ kfree(hub);
+ }
+}
+
static void hub_disconnect(struct usb_device *dev, void *ptr)
{
struct usb_hub *hub = (struct usb_hub *)ptr;
unsigned long flags;
+ if (hub->urb) {
+ usb_unlink_urb(hub->urb);
+ usb_free_urb(hub->urb);
+ hub->urb = NULL;
+ }
+
spin_lock_irqsave(&hub_event_lock, flags);
/* Delete it and then reset it */
spin_unlock_irqrestore(&hub_event_lock, flags);
- if (hub->urb) {
- usb_unlink_urb(hub->urb);
- usb_free_urb(hub->urb);
- hub->urb = NULL;
- }
-
- /* Free the memory */
- kfree(hub);
+ hub_put(hub);
}
-static int hub_ioctl (struct usb_device *hub, unsigned int code, void *user_data)
+static int hub_ioctl(struct usb_device *hub, unsigned int code, void *user_data)
{
/* assert ifno == 0 (part of hub spec) */
switch (code) {
unsigned long flags;
int i;
- spin_lock_irqsave (&hub_event_lock, flags);
+ spin_lock_irqsave(&hub_event_lock, flags);
if (hub->devnum <= 0)
info->nports = 0;
else {
info->nports = hub->maxchild;
for (i = 0; i < info->nports; i++) {
- if (hub->children [i] == NULL)
- info->port [i] = 0;
+ if (hub->children[i] == NULL)
+ info->port[i] = 0;
else
- info->port [i] = hub->children [i]->devnum;
+ info->port[i] = hub->children[i]->devnum;
}
}
- spin_unlock_irqrestore (&hub_event_lock, flags);
+ spin_unlock_irqrestore(&hub_event_lock, flags);
return info->nports + 1;
}
}
}
-static void usb_hub_port_connect_change(struct usb_device *hub, int port)
+static int usb_hub_reset(struct usb_hub *hub)
{
- struct usb_device *usb;
- struct usb_port_status portsts;
- unsigned short portstatus, portchange;
- int ret, tries;
-
- wait_ms(100);
+ struct usb_device *dev = hub->dev;
+ int i;
- ret = usb_get_port_status(hub, port + 1, &portsts);
- if (ret < 0) {
- err("get_port_status(%d) failed (err = %d)", port + 1, ret);
- return;
+ /* Disconnect any attached devices */
+ for (i = 0; i < hub->nports; i++) {
+ if (dev->children[i])
+ usb_disconnect(&dev->children[i]);
}
- portstatus = le16_to_cpu(portsts.wPortStatus);
- portchange = le16_to_cpu(portsts.wPortChange);
- dbg("portstatus %x, change %x, %s", portstatus, portchange,
- portstatus&(1<<USB_PORT_FEAT_LOWSPEED) ? "1.5 Mb/s" : "12 Mb/s");
+ /* Attempt to reset the hub */
+ if (hub->urb)
+ usb_unlink_urb(hub->urb);
+ else
+ return -1;
- /* Clear the connection change status */
- usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_CONNECTION);
+ if (usb_reset_device(dev))
+ return -1;
- /* Disconnect any existing devices under this port */
- if (((!(portstatus & USB_PORT_STAT_CONNECTION)) &&
- (!(portstatus & USB_PORT_STAT_ENABLE)))|| (hub->children[port])) {
- usb_disconnect(&hub->children[port]);
- /* Return now if nothing is connected */
- if (!(portstatus & USB_PORT_STAT_CONNECTION))
- return;
+ hub->urb->dev = dev;
+ if (usb_submit_urb(hub->urb))
+ return -1;
+
+ usb_hub_power_on(hub);
+
+ return 0;
+}
+
+static void usb_hub_disconnect(struct usb_device *dev)
+{
+ struct usb_device *parent = dev->parent;
+ int i;
+
+ /* Find the device pointer to disconnect */
+ if (parent) {
+ for (i = 0; i < parent->maxchild; i++) {
+ if (parent->children[i] == dev) {
+ usb_disconnect(&parent->children[i]);
+ return;
+ }
+ }
}
- wait_ms(400);
- down(&usb_address0_sem);
+ err("cannot disconnect hub %d", dev->devnum);
+}
-#define MAX_TRIES 5
- /* Reset the port */
- for (tries = 0; tries < MAX_TRIES ; tries++) {
- usb_set_port_feature(hub, port + 1, USB_PORT_FEAT_RESET);
- wait_ms(200);
+#define HUB_RESET_TRIES 5
+#define HUB_PROBE_TRIES 2
+#define HUB_SHORT_RESET_TIME 10
+#define HUB_LONG_RESET_TIME 200
+#define HUB_RESET_TIMEOUT 500
+static int usb_hub_port_wait_reset(struct usb_device *hub, int port,
+ struct usb_device *dev, unsigned int delay)
+{
+ int delay_time, ret;
+ struct usb_port_status portsts;
+ unsigned short portchange, portstatus;
+
+ for (delay_time = 0; delay_time < HUB_RESET_TIMEOUT; delay_time += delay) {
+ /* wait to give the device a chance to reset */
+ wait_ms(delay);
+
+ /* read and decode port status */
ret = usb_get_port_status(hub, port + 1, &portsts);
if (ret < 0) {
err("get_port_status(%d) failed (err = %d)", port + 1, ret);
- goto out;
+ return -1;
}
portstatus = le16_to_cpu(portsts.wPortStatus);
portchange = le16_to_cpu(portsts.wPortChange);
- dbg("portstatus %x, change %x, %s", portstatus ,portchange,
- portstatus&(1<<USB_PORT_FEAT_LOWSPEED) ? "1.5 Mb/s" : "12 Mb/s");
+ dbg("port %d, portstatus %x, change %x, %s", port + 1,
+ portstatus, portchange,
+ portstatus & (1 << USB_PORT_FEAT_LOWSPEED) ? "1.5 Mb/s" : "12 Mb/s");
+ /* bomb out completely if something weird happened */
if ((portchange & USB_PORT_STAT_C_CONNECTION) ||
!(portstatus & USB_PORT_STAT_CONNECTION))
- goto out;
+ return -1;
- if (portstatus & USB_PORT_STAT_ENABLE)
- break;
+ /* if we`ve finished resetting, then break out of the loop */
+ if (!(portstatus & USB_PORT_STAT_RESET) &&
+ (portstatus & USB_PORT_STAT_ENABLE)) {
+ dev->slow = (portstatus & USB_PORT_STAT_LOW_SPEED) ? 1 : 0;
+ return 0;
+ }
- wait_ms(200);
+ /* switch to the long delay after two short delay failures */
+ if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
+ delay = HUB_LONG_RESET_TIME;
+
+ dbg("port %d of hub %d not reset yet, waiting %dms", port + 1,
+ hub->devnum, delay);
}
- if (tries >= MAX_TRIES) {
- err("Cannot enable port %i after %i retries, disabling port.", port+1, MAX_TRIES);
- err("Maybe the USB cable is bad?");
- goto out;
+ return -1;
+}
+
+static int usb_hub_port_reset(struct usb_device *hub, int port,
+ struct usb_device *dev, unsigned int delay)
+{
+ int i;
+
+ /* Reset the port */
+ for (i = 0; i < HUB_RESET_TRIES; i++) {
+ usb_set_port_feature(hub, port + 1, USB_PORT_FEAT_RESET);
+
+ /* return success if the port reset OK */
+ if (!usb_hub_port_wait_reset(hub, port, dev, delay)) {
+ usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_RESET);
+ return 0;
+ }
+
+ dbg("port %d of hub %d not enabled, trying reset again...",
+ port + 1, hub->devnum);
+ delay = HUB_LONG_RESET_TIME;
}
- usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_RESET);
+ err("Cannot enable port %i of hub %d, disabling port.",
+ port + 1, hub->devnum);
+ err("Maybe the USB cable is bad?");
- /* Allocate a new device struct for it */
- usb = usb_alloc_dev(hub, hub->bus);
- if (!usb) {
- err("couldn't allocate usb_device");
- goto out;
+ return -1;
+}
+
+void usb_hub_port_disable(struct usb_device *hub, int port)
+{
+ int ret;
+
+ ret = usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_ENABLE);
+ if (ret)
+ err("cannot disable port %d of hub %d (err = %d)",
+ port + 1, hub->devnum, ret);
+}
+
+static void usb_hub_port_connect_change(struct usb_device *hub, int port,
+ struct usb_port_status *portsts)
+{
+ struct usb_device *dev;
+ unsigned short portstatus, portchange;
+ unsigned int delay = HUB_SHORT_RESET_TIME;
+ int i;
+ char *portstr, *tempstr;
+
+ portstatus = le16_to_cpu(portsts->wPortStatus);
+ portchange = le16_to_cpu(portsts->wPortChange);
+ dbg("port %d, portstatus %x, change %x, %s", port + 1, portstatus,
+ portchange, portstatus & (1 << USB_PORT_FEAT_LOWSPEED) ? "1.5 Mb/s" : "12 Mb/s");
+
+ /* Clear the connection change status */
+ usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_CONNECTION);
+
+ /* Disconnect any existing devices under this port */
+ if (hub->children[port])
+ usb_disconnect(&hub->children[port]);
+
+ /* Return now if nothing is connected */
+ if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
+ if (portstatus & USB_PORT_STAT_ENABLE)
+ usb_hub_port_disable(hub, port);
+
+ return;
}
- usb->slow = (portstatus & USB_PORT_STAT_LOW_SPEED) ? 1 : 0;
+ /* Some low speed devices have problems with the quick delay, so */
+ /* be a bit pessimistic with those devices. RHbug #23670 */
+ if (portstatus & USB_PORT_STAT_LOW_SPEED) {
+ wait_ms(400);
+ delay = HUB_LONG_RESET_TIME;
+ }
- hub->children[port] = usb;
+ down(&usb_address0_sem);
- /* Find a new device ID for it */
- usb_connect(usb);
+ tempstr = kmalloc(1024, GFP_KERNEL);
+ portstr = kmalloc(1024, GFP_KERNEL);
- /* Run it through the hoops (find a driver, etc) */
- ret = usb_new_device(usb);
- if (ret) {
- /* Try resetting the device. Windows does this and it */
- /* gets some devices working correctly */
- usb_set_port_feature(hub, port + 1, USB_PORT_FEAT_RESET);
+ for (i = 0; i < HUB_PROBE_TRIES; i++) {
+ struct usb_device *pdev, *cdev;
+
+ /* Allocate a new device struct */
+ dev = usb_alloc_dev(hub, hub->bus);
+ if (!dev) {
+ err("couldn't allocate usb_device");
+ break;
+ }
- ret = usb_new_device(usb);
- if (ret) {
- usb_disconnect(&hub->children[port]);
+ hub->children[port] = dev;
- /* Woops, disable the port */
- dbg("hub: disabling port %d", port + 1);
- usb_clear_port_feature(hub, port + 1,
- USB_PORT_FEAT_ENABLE);
+ /* Reset the device */
+ if (usb_hub_port_reset(hub, port, dev, delay)) {
+ usb_free_dev(dev);
+ break;
}
+
+ /* Find a new device ID for it */
+ usb_connect(dev);
+
+ /* Create a readable topology string */
+ cdev = dev;
+ pdev = dev->parent;
+ if (portstr && tempstr) {
+ portstr[0] = 0;
+ while (pdev) {
+ int port;
+
+ for (port = 0; port < pdev->maxchild; port++)
+ if (pdev->children[port] == cdev)
+ break;
+
+ strcpy(tempstr, portstr);
+ if (!strlen(tempstr))
+ sprintf(portstr, "%d", port + 1);
+ else
+ sprintf(portstr, "%d/%s", port + 1, tempstr);
+
+ cdev = pdev;
+ pdev = pdev->parent;
+ }
+ info("USB new device connect on bus%d/%s, assigned device number %d",
+ dev->bus->busnum, portstr, dev->devnum);
+ } else
+ info("USB new device connect on bus%d, assigned device number %d",
+ dev->bus->busnum, dev->devnum);
+
+ /* Run it through the hoops (find a driver, etc) */
+ if (!usb_new_device(dev))
+ goto done;
+
+ /* Free the configuration if there was an error */
+ usb_free_dev(dev);
+
+ /* Switch to a long reset time */
+ delay = HUB_LONG_RESET_TIME;
}
-out:
+ hub->children[port] = NULL;
+ usb_hub_port_disable(hub, port);
+done:
up(&usb_address0_sem);
+ if (portstr)
+ kfree(portstr);
+ if (tempstr)
+ kfree(tempstr);
}
static void usb_hub_events(void)
{
unsigned long flags;
- int i;
struct list_head *tmp;
struct usb_device *dev;
struct usb_hub *hub;
struct usb_hub_status hubsts;
unsigned short hubstatus, hubchange;
+ int i, ret;
/*
* We restart the list everytime to avoid a deadlock with
spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub_event_list))
- goto he_unlock;
+ break;
/* Grab the next entry from the beginning of the list */
tmp = hub_event_list.next;
list_del(tmp);
INIT_LIST_HEAD(tmp);
+ hub_get(hub);
spin_unlock_irqrestore(&hub_event_lock, flags);
+ if (hub->error) {
+ dbg("resetting hub %d for error %d", dev->devnum, hub->error);
+
+ if (usb_hub_reset(hub)) {
+ err("error resetting hub %d - disconnecting", dev->devnum);
+ usb_hub_disconnect(dev);
+ hub_put(hub);
+ continue;
+ }
+
+ hub->nerrors = 0;
+ hub->error = 0;
+ }
+
for (i = 0; i < hub->nports; i++) {
struct usb_port_status portsts;
unsigned short portstatus, portchange;
- if (usb_get_port_status(dev, i + 1, &portsts) < 0) {
- err("get_port_status failed");
+ ret = usb_get_port_status(dev, i + 1, &portsts);
+ if (ret < 0) {
+ err("get_port_status failed (err = %d)", ret);
continue;
}
if (portchange & USB_PORT_STAT_C_CONNECTION) {
dbg("port %d connection change", i + 1);
- usb_hub_port_connect_change(dev, i);
- }
-
- if (portchange & USB_PORT_STAT_C_ENABLE) {
+ usb_hub_port_connect_change(dev, i, &portsts);
+ } else if (portchange & USB_PORT_STAT_C_ENABLE) {
dbg("port %d enable change, status %x", i + 1, portstatus);
usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE);
- // EM interference sometimes causes bad shielded USB devices to
- // be shutdown by the hub, this hack enables them again.
- // Works at least with mouse driver.
+ /*
+ * EM interference sometimes causes bad shielded USB devices to
+ * be shutdown by the hub, this hack enables them again.
+ * Works at least with mouse driver.
+ */
if (!(portstatus & USB_PORT_STAT_ENABLE) &&
(portstatus & USB_PORT_STAT_CONNECTION) && (dev->children[i])) {
err("already running port %i disabled by hub (EMI?), re-enabling...",
i + 1);
- usb_hub_port_connect_change(dev, i);
+ usb_hub_port_connect_change(dev, i, &portsts);
}
}
- if (portstatus & USB_PORT_STAT_SUSPEND) {
+ if (portchange & USB_PORT_STAT_C_SUSPEND) {
dbg("port %d suspend change", i + 1);
- usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_SUSPEND);
+ usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_SUSPEND);
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
} /* end for i */
/* deal with hub status changes */
- if (usb_get_hub_status(dev, &hubsts) < 0) {
+ if (usb_get_hub_status(dev, &hubsts) < 0)
err("get_hub_status failed");
- } else {
+ else {
hubstatus = le16_to_cpup(&hubsts.wHubStatus);
hubchange = le16_to_cpup(&hubsts.wHubChange);
if (hubchange & HUB_CHANGE_LOCAL_POWER) {
}
if (hubchange & HUB_CHANGE_OVERCURRENT) {
dbg("hub overcurrent change");
- wait_ms(500); //Cool down
+ wait_ms(500); /* Cool down */
usb_clear_hub_feature(dev, C_HUB_OVER_CURRENT);
usb_hub_power_on(hub);
}
}
+ hub_put(hub);
} /* end while (1) */
-he_unlock:
spin_unlock_irqrestore(&hub_event_lock, flags);
}
static int usb_hub_thread(void *__hub)
{
- khubd_running = 1;
-
lock_kernel();
/*
} while (!signal_pending(current));
dbg("usb_hub_thread exiting");
- khubd_running = 0;
+ up(&khubd_exited);
return 0;
}
+
static struct usb_driver hub_driver = {
name: "hub",
probe: hub_probe,
/* Fall through if kernel_thread failed */
usb_deregister(&hub_driver);
+ err("failed to start usb_hub_thread");
return -1;
}
/* Kill the thread */
ret = kill_proc(khubd_pid, SIGTERM, 1);
- if (!ret) {
- /* Wait 10 seconds */
- int count = 10 * HZ;
- while (khubd_running && --count) {
- current->state = TASK_INTERRUPTIBLE;
- schedule_timeout(1);
- }
-
- if (!count)
- err("giving up on killing khubd");
- }
+ down(&khubd_exited);
/*
- * Hub resources are freed for us by usb_deregister. It
+ * Hub resources are freed for us by usb_deregister. It calls
* usb_driver_purge on every device which in turn calls that
* devices disconnect function if it is using this driver.
* The hub_disconnect function takes care of releasing the
down(&usb_address0_sem);
/* Send a reset to the device */
- usb_set_port_feature(parent, port + 1, USB_PORT_FEAT_RESET);
-
- wait_ms(200);
-
- usb_clear_port_feature(parent, port + 1, USB_PORT_FEAT_C_RESET);
+ if (usb_hub_port_reset(parent, port, dev, HUB_SHORT_RESET_TIME)) {
+ usb_hub_port_disable(parent, port);
+ up(&usb_address0_sem);
+ return(-ENODEV);
+ }
/* Reprogram the Address */
ret = usb_set_address(dev);
if (ret < 0) {
err("USB device not accepting new address (error=%d)", ret);
- clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
- dev->devnum = -1;
+ usb_hub_port_disable(parent, port);
up(&usb_address0_sem);
return ret;
}
- wait_ms(10); /* Let the SET_ADDRESS settle */
+ /* Let the SET_ADDRESS settle */
+ wait_ms(10);
up(&usb_address0_sem);
if (ret < 0)
err("unable to get device descriptor (error=%d)", ret);
else
- err("USB device descriptor short read (expected %i, got %i)", sizeof(dev->descriptor), ret);
+ err("USB device descriptor short read (expected %Zi, got %i)", sizeof(dev->descriptor), ret);
clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
dev->devnum = -1;
usb_set_maxpacket(dev);
return 1;
- } else {
- ret = usb_set_configuration(dev,
- dev->actconfig->bConfigurationValue);
- if (ret < 0) {
- err("failed to set active configuration (error=%d)",
- ret);
- return ret;
- }
+ }
- for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
- struct usb_interface *intf =
- &dev->actconfig->interface[i];
- struct usb_interface_descriptor *as =
- &intf->altsetting[intf->act_altsetting];
+ ret = usb_set_configuration(dev, dev->actconfig->bConfigurationValue);
+ if (ret < 0) {
+ err("failed to set active configuration (error=%d)", ret);
+ return ret;
+ }
- ret = usb_set_interface(dev, as->bInterfaceNumber,
- as->bAlternateSetting);
- if (ret < 0) {
- err("failed to set active alternate setting for interface %d (error=%d)", i, ret);
- return ret;
- }
- }
+ for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
+ struct usb_interface *intf = &dev->actconfig->interface[i];
+ struct usb_interface_descriptor *as = &intf->altsetting[intf->act_altsetting];
- return 0;
+ ret = usb_set_interface(dev, as->bInterfaceNumber, as->bAlternateSetting);
+ if (ret < 0) {
+ err("failed to set active alternate setting for interface %d (error=%d)", i, ret);
+ return ret;
+ }
}
return 0;
__u16 wHubCharacteristics;
__u8 bPwrOn2PwrGood;
__u8 bHubContrCurrent;
+
/* DeviceRemovable and PortPwrCtrlMask want to be variable-length
bitmaps that hold max 256 entries, but for now they're ignored */
+ __u8 bitmap[0];
} __attribute__ ((packed));
struct usb_device;
-typedef enum {
- USB_PORT_UNPOWERED = 0, /* Default state */
- USB_PORT_POWERED, /* When we've put power to it */
- USB_PORT_ENABLED, /* When it's been enabled */
- USB_PORT_DISABLED, /* If it's been disabled */
- USB_PORT_ADMINDISABLED, /* Forced down */
-} usb_hub_port_state;
-
-struct usb_hub_port {
- usb_hub_port_state cstate; /* Configuration state */
-
- struct usb_device *child; /* Device attached to this port */
-
- struct usb_hub *parent; /* Parent hub */
-};
-
struct usb_hub {
- /* Device structure */
struct usb_device *dev;
- /* Interrupt polling pipe */
- struct urb *urb;
+ struct urb *urb; /* Interrupt polling pipe */
char buffer[(USB_MAXCHILDREN + 1 + 7) / 8]; /* add 1 bit for hub status change */
/* and add 7 bits to round up to byte boundary */
+ int error;
+ int nerrors;
- /* List of hubs */
struct list_head hub_list;
- /* Temporary event list */
struct list_head event_list;
/* Number of ports on the hub */
int nports;
- struct usb_hub_port ports[0]; /* Dynamically allocated */
+ struct usb_hub_descriptor *descriptor;
+
+ atomic_t refcnt;
};
#endif
if (usb_register (&mdc800_usb_driver) < 0)
goto cleanup_on_fail;
- info (DRIVER_VERSION " " DRIVER_AUTHOR);
- info (DRIVER_DESC);
+ info (DRIVER_VERSION " " DRIVER_DESC);
return 0;
if (usb_register(&ov511_driver) < 0)
return -1;
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
*/
-#include <linux/module.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/usb.h>
+#include <linux/module.h>
+#include "pegasus.h"
-
-static const char *version = __FILE__ ": v0.4.13 2000/10/13 (C) 1999-2000 Petko Manolov (petkan@dce.bg)";
-
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.4.18 2001/03/18 (C) 1999-2000"
+#define DRIVER_AUTHOR "Petko Manolov <petkan@dce.bg>"
+#define DRIVER_DESC "ADMtek AN986 Pegasus USB Ethernet driver"
#define PEGASUS_USE_INTR
-
-
-#define PEGASUS_II 0x80000000
-#define HAS_HOME_PNA 0x40000000
-
-#define PEGASUS_MTU 1500
-#define PEGASUS_MAX_MTU 1536
-
-#define EPROM_WRITE 0x01
-#define EPROM_READ 0x02
-#define EPROM_DONE 0x04
-#define EPROM_WR_ENABLE 0x10
-#define EPROM_LOAD 0x20
-
-#define MII_BMCR 0x00
-#define MII_BMSR 0x01
-#define BMSR_MEDIA 0x7808
-#define MII_ANLPA 0x05
-#define ANLPA_100TX_FD 0x0100
-#define ANLPA_100TX_HD 0x0080
-#define ANLPA_10T_FD 0x0040
-#define ANLPA_10T_HD 0x0020
-#define PHY_DONE 0x80
-#define PHY_READ 0x40
-#define PHY_WRITE 0x20
-#define DEFAULT_GPIO_RESET 0x24
-#define LINKSYS_GPIO_RESET 0x24
-#define DEFAULT_GPIO_SET 0x26
-
-#define PEGASUS_PRESENT 0x00000001
-#define PEGASUS_RUNNING 0x00000002
-#define PEGASUS_TX_BUSY 0x00000004
-#define PEGASUS_RX_BUSY 0x00000008
-#define CTRL_URB_RUNNING 0x00000010
-#define CTRL_URB_SLEEP 0x00000020
-#define PEGASUS_UNPLUG 0x00000040
-#define ETH_REGS_CHANGE 0x40000000
-#define ETH_REGS_CHANGED 0x80000000
-
-#define RX_MULTICAST 2
-#define RX_PROMISCUOUS 4
-
-#define REG_TIMEOUT (HZ)
-#define PEGASUS_TX_TIMEOUT (HZ*10)
-
-#define TX_UNDERRUN 0x80
-#define EXCESSIVE_COL 0x40
-#define LATE_COL 0x20
-#define NO_CARRIER 0x10
-#define LOSS_CARRIER 0x08
-#define JABBER_TIMEOUT 0x04
-
-#define PEGASUS_REQT_READ 0xc0
-#define PEGASUS_REQT_WRITE 0x40
-#define PEGASUS_REQ_GET_REGS 0xf0
-#define PEGASUS_REQ_SET_REGS 0xf1
-#define PEGASUS_REQ_SET_REG PEGASUS_REQ_SET_REGS
-#define NUM_CTRL_URBS 0x10
-#define ALIGN(x) x __attribute__((aligned(L1_CACHE_BYTES)))
-
-enum pegasus_registers {
- EthCtrl0 = 0,
- EthCtrl1 = 1,
- EthCtrl2 = 2,
- EthID = 0x10,
- Reg1d = 0x1d,
- EpromOffset = 0x20,
- EpromData = 0x21, /* 0x21 low, 0x22 high byte */
- EpromCtrl = 0x23,
- PhyAddr = 0x25,
- PhyData = 0x26, /* 0x26 low, 0x27 high byte */
- PhyCtrl = 0x28,
- UsbStst = 0x2a,
- EthTxStat0 = 0x2b,
- EthTxStat1 = 0x2c,
- EthRxStat = 0x2d,
- Reg7b = 0x7b,
- Gpio0 = 0x7e,
- Gpio1 = 0x7f,
- Reg81 = 0x81,
-};
-
-
-typedef struct pegasus {
- struct usb_device *usb;
- struct net_device *net;
- struct net_device_stats stats;
- unsigned flags;
- unsigned features;
- int intr_interval;
- struct urb ctrl_urb, rx_urb, tx_urb, intr_urb;
- devrequest dr;
- wait_queue_head_t ctrl_wait;
- struct semaphore ctrl_sem;
- unsigned char ALIGN(rx_buff[PEGASUS_MAX_MTU]);
- unsigned char ALIGN(tx_buff[PEGASUS_MAX_MTU]);
- unsigned char ALIGN(intr_buff[8]);
- __u8 eth_regs[4];
- __u8 phy;
- __u8 gpio_res;
-} pegasus_t;
-
-struct usb_eth_dev {
- char *name;
- __u16 vendor;
- __u16 device;
- __u32 private; /* LSB is gpio reset value */
-};
-
+#define PEGASUS_WRITE_EEPROM
static int loopback = 0;
static int mii_mode = 0;
static int multicast_filter_limit = 32;
+static struct usb_eth_dev usb_dev_id[] = {
+#define PEGASUS_DEV(pn, vid, pid, flags) \
+ {name:pn, vendor:vid, device:pid, private:flags},
+#include "pegasus.h"
+#undef PEGASUS_DEV
+ {NULL, 0, 0, 0}
+};
+
-MODULE_AUTHOR("Petko Manolov <petkan@dce.bg>");
-MODULE_DESCRIPTION("ADMtek AN986 Pegasus USB Ethernet driver");
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_PARM(loopback, "i");
-MODULE_PARM(mode, "i");
+MODULE_PARM(mii_mode, "i");
MODULE_PARM_DESC(loopback, "Enable MAC loopback mode (bit 0)");
-MODULE_PARM_DESC(mode, "Enable HomePNA mode (bit 0) - default = MII mode = 0");
+MODULE_PARM_DESC(mii_mode, "Enable HomePNA mode (bit 0),default=MII mode = 0");
-static struct usb_eth_dev usb_dev_id[] = {
- {"Billionton USB-100", 0x08dd, 0x0986, DEFAULT_GPIO_RESET},
- {"Corega FEter USB-TX", 0x7aa, 0x0004, DEFAULT_GPIO_RESET},
- {"MELCO/BUFFALO LUA-TX", 0x0411, 0x0001, DEFAULT_GPIO_RESET},
- {"D-Link DSB-650TX", 0x2001, 0x4001, LINKSYS_GPIO_RESET},
- {"D-Link DSB-650TX", 0x2001, 0x4002, LINKSYS_GPIO_RESET},
- {"D-Link DSB-650TX(PNA)", 0x2001, 0x4003,
- HAS_HOME_PNA | DEFAULT_GPIO_RESET},
- {"D-Link DSB-650", 0x2001, 0xabc1, DEFAULT_GPIO_RESET},
- {"D-Link DU-E10", 0x07b8, 0xabc1, DEFAULT_GPIO_RESET},
- {"D-Link DU-E100", 0x07b8, 0x4002, DEFAULT_GPIO_RESET},
- {"Linksys USB10TX", 0x066b, 0x2202, LINKSYS_GPIO_RESET},
- {"Linksys USB100TX", 0x066b, 0x2203, LINKSYS_GPIO_RESET},
- {"Linksys USB100TX", 0x066b, 0x2204, HAS_HOME_PNA | LINKSYS_GPIO_RESET},
- {"Linksys USB Ethernet Adapter", 0x066b, 0x2206, LINKSYS_GPIO_RESET},
- {"SMC 202 USB Ethernet", 0x0707, 0x0200, DEFAULT_GPIO_RESET},
- {"ADMtek AN986 \"Pegasus\" USB Ethernet (eval board)", 0x07a6, 0x0986,
- HAS_HOME_PNA | DEFAULT_GPIO_RESET},
- {"Accton USB 10/100 Ethernet Adapter", 0x083a, 0x1046,
- DEFAULT_GPIO_RESET},
- {"IO DATA USB ET/TX", 0x04bb, 0x0904, DEFAULT_GPIO_RESET},
- {"LANEED USB Ethernet LD-USB/TX", 0x056e, 0x4002, DEFAULT_GPIO_RESET},
- {"SOHOware NUB100 Ethernet", 0x15e8, 0x9100, DEFAULT_GPIO_RESET},
- {"ADMtek ADM8511 \"Pegasus II\" USB Ethernet", 0x07a6, 0x8511,
- PEGASUS_II | DEFAULT_GPIO_RESET},
- {NULL, 0, 0, 0}
-};
-
static int update_eth_regs_async( pegasus_t * );
/* Aargh!!! I _really_ hate such tweaks */
static int get_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data)
{
int ret;
+ DECLARE_WAITQUEUE(wait, current);
- if ( pegasus->flags & ETH_REGS_CHANGED ) {
+ while ( pegasus->flags & ETH_REGS_CHANGED ) {
pegasus->flags |= CTRL_URB_SLEEP;
interruptible_sleep_on( &pegasus->ctrl_wait );
}
pegasus->dr.requesttype = PEGASUS_REQT_READ;
pegasus->dr.request = PEGASUS_REQ_GET_REGS;
- pegasus->dr.value = 0;
+ pegasus->dr.value = cpu_to_le16 (0);
pegasus->dr.index = cpu_to_le16p(&indx);
- pegasus->dr.length =
- pegasus->ctrl_urb.transfer_buffer_length = cpu_to_le16p(&size);
+ pegasus->dr.length = cpu_to_le16p(&size);
+ pegasus->ctrl_urb.transfer_buffer_length = size;
FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb,
usb_rcvctrlpipe(pegasus->usb,0),
(char *)&pegasus->dr,
data, size, ctrl_callback, pegasus );
+ add_wait_queue( &pegasus->ctrl_wait, &wait );
+ set_current_state( TASK_INTERRUPTIBLE );
+ pegasus->flags |= CTRL_URB_SLEEP;
+
if ( (ret = usb_submit_urb( &pegasus->ctrl_urb )) ) {
err( __FUNCTION__ " BAD CTRLs %d", ret);
goto out;
}
- pegasus->flags |= CTRL_URB_SLEEP;
- interruptible_sleep_on( &pegasus->ctrl_wait );
+
+ schedule();
+ remove_wait_queue( &pegasus->ctrl_wait, &wait );
out:
return ret;
}
static int set_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data)
{
int ret;
+ DECLARE_WAITQUEUE(wait, current);
- if ( pegasus->flags & ETH_REGS_CHANGED ) {
+ while ( pegasus->flags & ETH_REGS_CHANGED ) {
pegasus->flags |= CTRL_URB_SLEEP ;
interruptible_sleep_on( &pegasus->ctrl_wait );
}
pegasus->dr.requesttype = PEGASUS_REQT_WRITE;
pegasus->dr.request = PEGASUS_REQ_SET_REGS;
- pegasus->dr.value = 0;
+ pegasus->dr.value = cpu_to_le16 (0);
pegasus->dr.index = cpu_to_le16p( &indx );
- pegasus->dr.length =
- pegasus->ctrl_urb.transfer_buffer_length = cpu_to_le16p( &size );
+ pegasus->dr.length = cpu_to_le16p( &size );
+ pegasus->ctrl_urb.transfer_buffer_length = size;
FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb,
usb_sndctrlpipe(pegasus->usb,0),
(char *)&pegasus->dr,
data, size, ctrl_callback, pegasus );
+
+ add_wait_queue( &pegasus->ctrl_wait, &wait );
+ set_current_state( TASK_INTERRUPTIBLE );
+ pegasus->flags |= CTRL_URB_SLEEP;
if ( (ret = usb_submit_urb( &pegasus->ctrl_urb )) ) {
err( __FUNCTION__ " BAD CTRL %d", ret);
return ret;
}
- pegasus->flags |= CTRL_URB_SLEEP;
- interruptible_sleep_on( &pegasus->ctrl_wait );
+
+ schedule();
+ remove_wait_queue( &pegasus->ctrl_wait, &wait );
return ret;
}
static int set_register( pegasus_t *pegasus, __u16 indx, __u8 data )
{
int ret;
-
- if ( pegasus->flags & ETH_REGS_CHANGED ) {
+ __u16 dat = data;
+ DECLARE_WAITQUEUE(wait, current);
+
+ while ( pegasus->flags & ETH_REGS_CHANGED ) {
pegasus->flags |= CTRL_URB_SLEEP;
interruptible_sleep_on( &pegasus->ctrl_wait );
}
pegasus->dr.requesttype = PEGASUS_REQT_WRITE;
pegasus->dr.request = PEGASUS_REQ_SET_REG;
- pegasus->dr.value = data;
+ pegasus->dr.value = cpu_to_le16p( &dat);
pegasus->dr.index = cpu_to_le16p( &indx );
- pegasus->dr.length = pegasus->ctrl_urb.transfer_buffer_length = 1;
+ pegasus->dr.length = cpu_to_le16( 1 );
+ pegasus->ctrl_urb.transfer_buffer_length = 1;
FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb,
usb_sndctrlpipe(pegasus->usb,0),
(char *)&pegasus->dr,
&data, 1, ctrl_callback, pegasus );
+ add_wait_queue( &pegasus->ctrl_wait, &wait );
+ set_current_state( TASK_INTERRUPTIBLE );
+ pegasus->flags |= CTRL_URB_SLEEP;
+
if ( (ret = usb_submit_urb( &pegasus->ctrl_urb )) ) {
err( __FUNCTION__ " BAD CTRL %d", ret);
return ret;
}
- pegasus->flags |= CTRL_URB_SLEEP;
- interruptible_sleep_on( &pegasus->ctrl_wait );
-
+
+ schedule();
+ remove_wait_queue( &pegasus->ctrl_wait, &wait );
+
return ret;
}
pegasus->dr.requesttype = PEGASUS_REQT_WRITE;
pegasus->dr.request = PEGASUS_REQ_SET_REGS;
pegasus->dr.value = 0;
- pegasus->dr.index = EthCtrl0;
- pegasus->dr.length =
+ pegasus->dr.index = cpu_to_le16(EthCtrl0);
+ pegasus->dr.length = cpu_to_le16(3);
pegasus->ctrl_urb.transfer_buffer_length = 3;
FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb,
}
-static int read_phy_word( pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd )
+static int read_mii_word( pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd )
{
int i;
__u8 data[4] = { phy, 0, 0, indx };
-
+ __u16 regdi;
+
set_register( pegasus, PhyCtrl, 0 );
set_registers( pegasus, PhyAddr, sizeof(data), data );
set_register( pegasus, PhyCtrl, (indx | PHY_READ) );
break;
}
if ( i < REG_TIMEOUT ) {
- get_registers( pegasus, PhyData, 2, regd );
+ get_registers( pegasus, PhyData, 2, ®di );
+ *regd = le16_to_cpu(regdi);
return 0;
}
warn( __FUNCTION__ " failed" );
}
-static int write_phy_word( pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 regd )
+static int write_mii_word( pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 regd )
{
int i;
__u8 data[4] = { phy, 0, 0, indx };
static int read_eprom_word( pegasus_t *pegasus, __u8 index, __u16 *retdata )
{
- int i, tmp;
-
+ int i;
+ __u8 tmp;
+ __u16 retdatai;
+
set_register( pegasus, EpromCtrl, 0 );
set_register( pegasus, EpromOffset, index );
set_register( pegasus, EpromCtrl, EPROM_READ);
+
for ( i=0; i < REG_TIMEOUT; i++ ) {
get_registers( pegasus, EpromCtrl, 1, &tmp );
if ( tmp & EPROM_DONE )
break;
}
if ( i < REG_TIMEOUT ) {
- get_registers( pegasus, EpromData, 2, retdata );
+ get_registers( pegasus, EpromData, 2, &retdatai );
+ *retdata = le16_to_cpu (retdatai);
return 0;
}
warn( __FUNCTION__ " failed" );
static inline void get_node_id( pegasus_t *pegasus, __u8 *id )
{
int i;
-
- for (i = 0; i < 3; i++)
- read_eprom_word( pegasus, i, (__u16 *)&id[i*2]);
+ __u16 w16;
+
+ for (i = 0; i < 3; i++) {
+ read_eprom_word( pegasus, i, &w16);
+ ((__u16 *) id)[i] = cpu_to_le16p (&w16);
+ }
}
}
if ( i == REG_TIMEOUT )
return 1;
+
+ if ( usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
+ usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK1 ) {
+ __u16 auxmode;
+
+ read_mii_word( pegasus, 0, 0x1b, &auxmode );
+ write_mii_word( pegasus, 0, 0x1b, auxmode | 4 );
+ }
+
return 0;
}
pegasus_t *pegasus = dev->priv;
- if ( read_phy_word(pegasus, pegasus->phy, MII_BMSR, &bmsr) )
- return 2;
+ if ( read_mii_word(pegasus, pegasus->phy, MII_BMSR, &bmsr) )
+ return 1;
if ( !(bmsr & 0x20) && !loopback )
warn( "%s: link NOT established (0x%x) - check the cable.",
dev->name, bmsr );
- if ( read_phy_word(pegasus, pegasus->phy, MII_ANLPA, &linkpart) )
- return 4;
+ if ( read_mii_word(pegasus, pegasus->phy, MII_ANLPA, &linkpart) )
+ return 2;
if ( !(linkpart & 1) )
warn( "link partner stat %x", linkpart );
data[1] = 0;
data[2] = (loopback & 1) ? 0x09 : 0x01;
- *(unsigned *)pegasus->eth_regs = *(unsigned *)data;
+ memcpy( pegasus->eth_regs, data, sizeof(data) );
set_registers( pegasus, EthCtrl0, 3, data );
if ( pegasus->flags & PEGASUS_RX_BUSY ) {
pegasus->stats.rx_errors++;
+ dbg("pegasus Rx busy");
return;
}
pegasus->flags |= PEGASUS_RX_BUSY;
- rx_status = *(int *)(pegasus->rx_buff + count - 4);
-
- if (urb->status) {
- dbg("%s: RX status %d", net->name, urb->status);
- goto goon;
+ switch ( urb->status ) {
+ case USB_ST_NOERROR:
+ break;
+ case USB_ST_NORESPONSE:
+ dbg( "reset MAC" );
+ pegasus->flags &= ~PEGASUS_RX_BUSY;
+ break;
+ default:
+ dbg( "%s: RX status %d", net->name, urb->status );
+ goto goon;
}
if ( !count )
goto goon;
+ rx_status = le32_to_cpu(*(int *)(pegasus->rx_buff + count - 4));
if ( rx_status & 0x000e0000 ) {
-
- dbg("%s: error receiving packet %x", net->name, rx_status & 0xe0000);
+ dbg("%s: RX packet error %x", net->name, rx_status & 0xe0000);
pegasus->stats.rx_errors++;
if ( rx_status & 0x060000 )
pegasus->stats.rx_length_errors++;
pegasus->stats.rx_crc_errors++;
if ( rx_status & 0x100000 )
pegasus->stats.rx_frame_errors++;
-
goto goon;
}
if ( urb->status )
info("%s: TX status %d", pegasus->net->name, urb->status);
+ pegasus->net->trans_start = jiffies;
netif_wake_queue( pegasus->net );
}
if ( !pegasus )
return;
+
+ switch ( urb->status ) {
+ case USB_ST_NOERROR:
+ break;
+ case USB_ST_URB_KILLED:
+ return;
+ default:
+ info("intr status %d", urb->status);
+ }
+
d = urb->transfer_buffer;
net = pegasus->net;
if ( d[0] & 0xfc ) {
if ( d[0] & (NO_CARRIER | LOSS_CARRIER) )
pegasus->stats.tx_carrier_errors++;
}
- switch ( urb->status ) {
- case USB_ST_NOERROR:
- break;
- case USB_ST_URB_KILLED:
- break;
- default:
- info("intr status %d", urb->status);
- }
}
#endif
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,48)
static void pegasus_tx_timeout( struct net_device *net )
{
pegasus_t *pegasus = net->priv;
if ( !pegasus )
return;
-
- usb_unlink_urb( &pegasus->tx_urb );
+
warn("%s: Tx timed out.", net->name);
+ pegasus->tx_urb.transfer_flags |= USB_ASYNC_UNLINK;
+ usb_unlink_urb( &pegasus->tx_urb );
pegasus->stats.tx_errors++;
- net->trans_start = jiffies;
-
- netif_wake_queue( net );
}
+#endif
static int pegasus_start_xmit( struct sk_buff *skb, struct net_device *net )
pegasus_t *pegasus = net->priv;
int count = ((skb->len+2) & 0x3f) ? skb->len+2 : skb->len+3;
int res;
-
+ __u16 l16 = skb->len;
+
netif_stop_queue( net );
- ((__u16 *)pegasus->tx_buff)[0] = skb->len;
+ ((__u16 *)pegasus->tx_buff)[0] = cpu_to_le16( l16 );
memcpy(pegasus->tx_buff+2, skb->data, skb->len);
FILL_BULK_URB( &pegasus->tx_urb, pegasus->usb,
usb_sndbulkpipe(pegasus->usb, 2),
pegasus->tx_buff, PEGASUS_MAX_MTU,
write_bulk_callback, pegasus );
pegasus->tx_urb.transfer_buffer_length = count;
- pegasus->tx_urb.transfer_flags |= USB_ASYNC_UNLINK;
if ((res = usb_submit_urb(&pegasus->tx_urb))) {
warn("failed tx_urb %d", res);
pegasus->stats.tx_errors++;
__u8 data[2];
read_eprom_word( pegasus, 4, (__u16 *)data );
+ if ( data[1] < 0x80 ) {
+ info( "intr interval will be changed from %ums to %ums",
+ data[1], 0x80 );
+ data[1] = 0x80;
+#ifdef PEGASUS_WRITE_EEPROM
+ write_eprom_word( pegasus, 4, *(__u16 *)data );
+#endif
+ }
pegasus->intr_interval = data[1];
}
if ( (res = usb_submit_urb(&pegasus->rx_urb)) )
warn( __FUNCTION__ " failed rx_urb %d", res );
#ifdef PEGASUS_USE_INTR
- get_interrupt_interval( pegasus );
FILL_INT_URB( &pegasus->intr_urb, pegasus->usb,
usb_rcvintpipe(pegasus->usb, 3),
pegasus->intr_buff, sizeof(pegasus->intr_buff),
usb_unlink_urb( &pegasus->rx_urb );
usb_unlink_urb( &pegasus->tx_urb );
usb_unlink_urb( &pegasus->ctrl_urb );
+#ifdef PEGASUS_USE_INTR
usb_unlink_urb( &pegasus->intr_urb );
-
+#endif
MOD_DEC_USE_COUNT;
return 0;
case SIOCDEVPRIVATE:
data[0] = pegasus->phy;
case SIOCDEVPRIVATE+1:
- read_phy_word(pegasus, data[0], data[1]&0x1f, &data[3]);
+ read_mii_word(pegasus, data[0], data[1]&0x1f, &data[3]);
return 0;
case SIOCDEVPRIVATE+2:
if ( !capable(CAP_NET_ADMIN) )
return -EPERM;
- write_phy_word(pegasus, pegasus->phy, data[1] & 0x1f, data[2]);
+ write_mii_word(pegasus, pegasus->phy, data[1] & 0x1f, data[2]);
return 0;
default:
return -EOPNOTSUPP;
__u16 tmp;
for ( i=0; i < 32; i++ ) {
- read_phy_word( pegasus, i, MII_BMSR, &tmp );
+ read_mii_word( pegasus, i, MII_BMSR, &tmp );
if ( tmp == 0 || tmp == 0xffff || (tmp & BMSR_MEDIA) == 0 )
continue;
else
return i;
}
- return 0;
+ return 0xff;
}
static void * pegasus_probe( struct usb_device *dev, unsigned int ifnum )
{
- struct net_device *net;
- pegasus_t *pegasus;
- int dev_indx;
+ struct net_device *net;
+ pegasus_t *pegasus;
+ int dev_index;
- if ( (dev_indx = check_device_ids(dev->descriptor.idVendor, dev->descriptor.idProduct)) == -1 ) {
+ if ( (dev_index = check_device_ids(dev->descriptor.idVendor, dev->descriptor.idProduct)) == -1 ) {
return NULL;
}
usb_inc_dev_use( dev );
memset(pegasus, 0, sizeof(struct pegasus));
- init_MUTEX( &pegasus-> ctrl_sem );
+ pegasus->dev_index = dev_index;
init_waitqueue_head( &pegasus->ctrl_wait );
net = init_etherdev( NULL, 0 );
net->get_stats = pegasus_netdev_stats;
net->mtu = PEGASUS_MTU;
- pegasus->features = usb_dev_id[dev_indx].private;
+ pegasus->features = usb_dev_id[dev_index].private;
+#ifdef PEGASUS_USE_INTR
+ get_interrupt_interval( pegasus );
+#endif
if ( reset_mac(pegasus) ) {
err("can't reset MAC");
unregister_netdev( pegasus->net );
return NULL;
}
+ info( "%s: %s", net->name, usb_dev_id[dev_index].name );
+
set_ethernet_addr( pegasus );
-
+
if ( pegasus->features & PEGASUS_II ) {
info( "setup Pegasus II specific registers" );
setup_pegasus_II( pegasus );
}
pegasus->phy = mii_phy_probe( pegasus );
- if ( !pegasus->phy ) {
+ if ( pegasus->phy == 0xff ) {
warn( "can't locate MII phy, using default" );
pegasus->phy = 1;
}
- info( "%s: %s", net->name, usb_dev_id[dev_indx].name );
-
return pegasus;
}
int __init pegasus_init(void)
{
- info( "%s", version );
+ info(DRIVER_VERSION " " DRIVER_AUTHOR);
+ info(DRIVER_DESC);
return usb_register( &pegasus_driver );
}
--- /dev/null
+/*
+ * Copyright (c) 1999,2000 Petko Manolov - Petkan (petkan@dce.bg)
+ *
+ * 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 PEGASUS_DEV
+
+#define PEGASUS_II 0x80000000
+#define HAS_HOME_PNA 0x40000000
+
+#define PEGASUS_MTU 1500
+#define PEGASUS_MAX_MTU 1536
+
+#define EPROM_WRITE 0x01
+#define EPROM_READ 0x02
+#define EPROM_DONE 0x04
+#define EPROM_WR_ENABLE 0x10
+#define EPROM_LOAD 0x20
+
+#define MII_BMCR 0x00
+#define MII_BMSR 0x01
+#define BMSR_MEDIA 0x7808
+#define MII_ANLPA 0x05
+#define ANLPA_100TX_FD 0x0100
+#define ANLPA_100TX_HD 0x0080
+#define ANLPA_10T_FD 0x0040
+#define ANLPA_10T_HD 0x0020
+#define PHY_DONE 0x80
+#define PHY_READ 0x40
+#define PHY_WRITE 0x20
+#define DEFAULT_GPIO_RESET 0x24
+#define LINKSYS_GPIO_RESET 0x24
+#define DEFAULT_GPIO_SET 0x26
+
+#define PEGASUS_PRESENT 0x00000001
+#define PEGASUS_RUNNING 0x00000002
+#define PEGASUS_TX_BUSY 0x00000004
+#define PEGASUS_RX_BUSY 0x00000008
+#define CTRL_URB_RUNNING 0x00000010
+#define CTRL_URB_SLEEP 0x00000020
+#define PEGASUS_UNPLUG 0x00000040
+#define ETH_REGS_CHANGE 0x40000000
+#define ETH_REGS_CHANGED 0x80000000
+
+#define RX_MULTICAST 2
+#define RX_PROMISCUOUS 4
+
+#define REG_TIMEOUT (HZ)
+#define PEGASUS_TX_TIMEOUT (HZ*10)
+
+#define TX_UNDERRUN 0x80
+#define EXCESSIVE_COL 0x40
+#define LATE_COL 0x20
+#define NO_CARRIER 0x10
+#define LOSS_CARRIER 0x08
+#define JABBER_TIMEOUT 0x04
+
+#define PEGASUS_REQT_READ 0xc0
+#define PEGASUS_REQT_WRITE 0x40
+#define PEGASUS_REQ_GET_REGS 0xf0
+#define PEGASUS_REQ_SET_REGS 0xf1
+#define PEGASUS_REQ_SET_REG PEGASUS_REQ_SET_REGS
+#define ALIGN(x) x __attribute__((aligned(L1_CACHE_BYTES)))
+
+enum pegasus_registers {
+ EthCtrl0 = 0,
+ EthCtrl1 = 1,
+ EthCtrl2 = 2,
+ EthID = 0x10,
+ Reg1d = 0x1d,
+ EpromOffset = 0x20,
+ EpromData = 0x21, /* 0x21 low, 0x22 high byte */
+ EpromCtrl = 0x23,
+ PhyAddr = 0x25,
+ PhyData = 0x26, /* 0x26 low, 0x27 high byte */
+ PhyCtrl = 0x28,
+ UsbStst = 0x2a,
+ EthTxStat0 = 0x2b,
+ EthTxStat1 = 0x2c,
+ EthRxStat = 0x2d,
+ Reg7b = 0x7b,
+ Gpio0 = 0x7e,
+ Gpio1 = 0x7f,
+ Reg81 = 0x81,
+};
+
+
+typedef struct pegasus {
+ struct usb_device *usb;
+ struct net_device *net;
+ struct net_device_stats stats;
+ unsigned flags;
+ unsigned features;
+ int dev_index;
+ int intr_interval;
+ struct urb ctrl_urb, rx_urb, tx_urb, intr_urb;
+ devrequest dr;
+ wait_queue_head_t ctrl_wait;
+ struct semaphore ctrl_sem;
+ unsigned char ALIGN(rx_buff[PEGASUS_MAX_MTU]);
+ unsigned char ALIGN(tx_buff[PEGASUS_MAX_MTU]);
+ unsigned char ALIGN(intr_buff[8]);
+ __u8 eth_regs[4];
+ __u8 phy;
+ __u8 gpio_res;
+} pegasus_t;
+
+
+struct usb_eth_dev {
+ char *name;
+ __u16 vendor;
+ __u16 device;
+ __u32 private; /* LSB is gpio reset value */
+};
+
+#define VENDOR_3COM 0x0506
+#define VENDOR_ACCTON 0x083a
+#define VENDOR_ADMTEK 0x07a6
+#define VENDOR_BILLIONTON 0x08dd
+#define VENDOR_COREGA 0x07aa
+#define VENDOR_DLINK1 0x2001
+#define VENDOR_DLINK2 0x07b8
+#define VENDOR_IODATA 0x04bb
+#define VENDOR_LANEED 0x056e
+#define VENDOR_LINKSYS 0x066b
+#define VENDOR_MELCO 0x0411
+#define VENDOR_SMARTBRIDGES 0x08d1
+#define VENDOR_SMC 0x0707
+#define VENDOR_SOHOWARE 0x15e8
+
+#else /* PEGASUS_DEV */
+
+PEGASUS_DEV( "3Com USB Ethernet 3C460B", VENDOR_3COM, 0x4601,
+ DEFAULT_GPIO_RESET | PEGASUS_II )
+PEGASUS_DEV( "Accton USB 10/100 Ethernet Adapter", VENDOR_ACCTON, 0x1046,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "ADMtek ADM8511 \"Pegasus II\" USB Ethernet",
+ VENDOR_ADMTEK, 0x8511,
+ DEFAULT_GPIO_RESET | PEGASUS_II )
+PEGASUS_DEV( "ADMtek AN986 \"Pegasus\" USB Ethernet (eval board)",
+ VENDOR_ADMTEK, 0x0986,
+ DEFAULT_GPIO_RESET | HAS_HOME_PNA )
+PEGASUS_DEV( "Billionton USB-100", VENDOR_BILLIONTON, 0x0986,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "Billionton USBLP-100", VENDOR_BILLIONTON, 0x0987,
+ DEFAULT_GPIO_RESET | HAS_HOME_PNA )
+PEGASUS_DEV( "Billionton USBEL-100", VENDOR_BILLIONTON, 0x0988,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "Billionton USBE-100", VENDOR_BILLIONTON, 0x8511,
+ DEFAULT_GPIO_RESET | PEGASUS_II )
+PEGASUS_DEV( "Corega FEter USB-TX", VENDOR_COREGA, 0x0004,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "D-Link DSB-650TX", VENDOR_DLINK1, 0x4001,
+ LINKSYS_GPIO_RESET )
+PEGASUS_DEV( "D-Link DSB-650TX", VENDOR_DLINK1, 0x4002,
+ LINKSYS_GPIO_RESET )
+PEGASUS_DEV( "D-Link DSB-650TX(PNA)", VENDOR_DLINK1, 0x4003,
+ DEFAULT_GPIO_RESET | HAS_HOME_PNA )
+PEGASUS_DEV( "D-Link DSB-650", VENDOR_DLINK1, 0xabc1,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "D-Link DU-E10", VENDOR_DLINK2, 0xabc1,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "D-Link DU-E100", VENDOR_DLINK2, 0x4002,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "FiberLine USB", VENDOR_DLINK2, 0x4102,
+ DEFAULT_GPIO_RESET | PEGASUS_II )
+PEGASUS_DEV( "IO DATA USB ET/TX", VENDOR_IODATA, 0x0904,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "LANEED USB Ethernet LD-USB/TX", VENDOR_LANEED, 0x4002,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "Linksys USB10TX", VENDOR_LINKSYS, 0x2202,
+ LINKSYS_GPIO_RESET )
+PEGASUS_DEV( "Linksys USB100TX", VENDOR_LINKSYS, 0x2203,
+ LINKSYS_GPIO_RESET )
+PEGASUS_DEV( "Linksys USB100TX", VENDOR_LINKSYS, 0x2204,
+ LINKSYS_GPIO_RESET | HAS_HOME_PNA )
+PEGASUS_DEV( "Linksys USB Ethernet Adapter", VENDOR_LINKSYS, 0x2206,
+ LINKSYS_GPIO_RESET )
+PEGASUS_DEV( "MELCO/BUFFALO LUA-TX", VENDOR_MELCO, 0x0001,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "smartNIC 2 PnP Adapter", VENDOR_SMARTBRIDGES, 0x0003,
+ DEFAULT_GPIO_RESET | PEGASUS_II )
+PEGASUS_DEV( "SMC 202 USB Ethernet", VENDOR_SMC, 0x0200,
+ DEFAULT_GPIO_RESET )
+PEGASUS_DEV( "SOHOware NUB100 Ethernet", VENDOR_SOHOWARE, 0x9100,
+ DEFAULT_GPIO_RESET )
+
+#endif /* PEGASUS_DEV */
/*****************************************************************************/
/*
- * plusb.c -- prolific pl-2302 driver.
+ * plusb.c -- prolific pl-2301/pl-2302 driver.
*
* Copyright (C) 2000 Deti Fliegl (deti@fliegl.de)
+ * Copyright (C) 2000 Pavel Machek (pavel@suse.cz)
+ * Copyright (C) 2000 Eric Z. Ayers (eric@compgen.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
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
+ * This driver creates a network interface (plusb0, plusb1, ...) that will
+ * send messages over a USB host-host cable based on the Prolific ASIC.
+ * It works a lot like plip or PP over an RS-232C null modem cable.
+ *
+ * Expect speeds of around 330Kbytes/second over a UHCI host controller.
+ * OHCI should be faster. Increase the MTU for faster transfers of large
+ * files (up-to 800Kbytes/second). (16384 is a good size)
*
* $Id: plusb.c,v 1.18 2000/02/14 10:38:58 fliegl Exp $
*
+ * Changelog:
+ *
+ * v0.1 deti
+ * Original Version of driver.
+ * v0.2 15 Sep 2000 pavel
+ * Patches to decrease latency by rescheduling the bottom half of
+ * interrupt code.
+ * v0.3 10 Oct 2000 eric
+ * Patches to work in v2.2 backport (v2.4 changes the way net_dev.name
+ * is allocated)
+ * v0.4 19 Oct 2000 eric
+ * Some more performance fixes. Lock re-submitting urbs.
+ * Lower the number of sk_buff's to queue.
+ * v0.5 25 Oct 2000 eric
+ * Removed use of usb_bulk_msg() all together. This caused
+ * the driver to block in an interrupt context.
+ * Consolidate read urb submission into read_urb_submit().
+ * Performance is the same as v0.4.
+ * v0.5.1 27 Oct 2000 eric
+ * Extra debugging messages to help diagnose problem with uchi.o stack.
+ * v0.5.2 27 Oct 2000 eric
+ * Set the 'start' flag for the network device in plusb_net_start()
+ * and plusb_net_stop() (doesn't help)
+ * v0.5.3 27 Oct 2000 pavel
+ * Commented out handlers when -EPIPE is received,
+ * (remove calls to usb_clear_halt()) Since the callback is in
+ * an interrupt context, it doesn't help, it just panics
+ * the kernel. (what do we do?)
+ * Under high load, dev_alloc_skb() fails, the read URB must
+ * be re-submitted.
+ * Added plusb_change_mtu() and increased the size of _BULK_DATA_LEN
+ * v0.5.4 31 Oct 2000 eric
+ * Fix race between plusb_net_xmit() and plusb_bulk_write_complete()
+ * v0.5.5 1 Nov 2000 eric
+ * Remove dev->start field, otherwise, it won't compile in 2.4
+ * Use dev_kfree_skb_any(). (important in 2.4 kernel)
+ * v0.5.6 2 Nov 2000 pavel,eric
+ * Add calls to netif_stop_queue() and netif_start_queue()
+ * Drop packets that come in while the free list is empty.
+ * (This version is being submitted after the release of 2.4-test10)
+ * v0.5.7 6 Nov 2000
+ * Fix to not re-submit the urb on error to help when cables
+ * are yanked (not tested)
+ *
+ *
+ * KNOWN PROBLEMS: (Any suggestions greatfully accepted!)
+ *
+ * 2 Nov 2000
+ * - The shutdown for this may not be entirely clean. Sometimes, the
+ * kernel will Oops when the cable is unplugged, or
+ * if the plusb module is removed.
+ * - If you ifdown a device and then ifup it again, the link will not
+ * always work. You have to 'rmmod plusb ; modprobe plusb' on
+ * both machines to get it to work again. Something must be wrong with
+ * plusb_net_open() and plusb_net_start() ? Maybe
+ * the 'suspend' and 'resume' entry points need to be
+ * implemented?
+ * - Needs to handle -EPIPE correctly in bulk complete handlers.
+ * (replace usb_clear_halt() function with async urbs?)
+ * - I think this code relies too much on one spinlock and does
+ * too much in the interrupt handler. The net1080 code is
+ * much more elegant, and should work for this chip. Its
+ * only drawback is that it is going to be tough to backport
+ * it to v2.2.
+ * - Occasionally the device will hang under the 'uhci.o'
+ * driver. The workaround is to ifdown the device and
+ * remove the modules, then re-insert them. You may have
+ * better luck with the 'usb-uhci.o' driver.
+ * - After using ifconfig down ; ifconfig up, sometimes packets
+ * continue to be received, but there is a framing problem.
+ *
+ * FUTURE DIRECTIONS:
+ *
+ * - Fix the known problems.
+ * - There isn't much functional difference between the net1080
+ * driver and this one. It would be neat if the same driver
+ * could handle both types of chips. Or if both drivers
+ * could handle both types of chips - this one is easier to
+ * backport to the 2.2 kernel.
+ * - Get rid of plusb_add_buf_tail and the single spinlock.
+ * Use a separate spinlock for the 2 lists, and use atomic
+ * operators for writeurb_submitted and readurb_submitted members.
+ *
+ *
*/
/*****************************************************************************/
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
-#define DEBUG
+//#define DEBUG 1
#include <linux/usb.h>
-#include "plusb.h"
+#if (LINUX_VERSION_CODE < 0x020300)
+#define dev_kfree_skb_any dev_kfree_skb
+#endif
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.5.7"
+#define DRIVER_AUTHOR "Deti Fliegl, deti@fliegl.de"
+#define DRIVER_DESC "PL-2302 USB Interface Driver for Linux (c)2000"
+
+/* Definitions formerly in plusb.h relocated. No need to export them -EZA */
+
+#define _PLUSB_INTPIPE 0x1
+#define _PLUSB_BULKOUTPIPE 0x2
+#define _PLUSB_BULKINPIPE 0x3
+
+#define _SKB_NUM 32
+
+/* increase size of BULK_DATA_LEN so we can use bigger MTU's*/
+#define _BULK_DATA_LEN 32768
+
+
+typedef struct
+{
+ int connected; /* indicates if this structure is active */
+ struct usb_device *usbdev;
+ /* keep track of USB structure */
+ int status; /* Prolific status byte returned from interrupt */
+ int in_bh; /* flag to indicate that we are in the bulk handler */
+ int opened; /* flag to indicate that network dev is open */
+
+ spinlock_t lock; /* Lock for the buffer list. re-used for
+ locking around submitting the readurb member.
+ */
+ urb_t *inturb; /* Read buffer for the interrupt callback */
+ unsigned char * interrupt_in_buffer;
+ /* holds data for the inturb*/
+ urb_t *readurb; /* Read buffer for the bulk data callback */
+ unsigned char * bulk_in_buffer;
+ /* kmalloc'ed data for the readurb */
+ int readurb_submitted;
+ /* Flag to indicate that readurb already sent */
+ urb_t *writeurb; /* Write buffer for the bulk data callback */
+ int writeurb_submitted;
+ /* Flag to indicate that writeurb already sent */
+
+ struct list_head tx_skb_list;
+ /* sk_buff's read from net device */
+ struct list_head free_skb_list;
+ /* free sk_buff list */
+ struct net_device net_dev;
+ /* handle to linux network device */
+ struct net_device_stats net_stats;
+ /* stats to return for ifconfig output */
+} plusb_t,*pplusb_t;
+
+/*
+ * skb_list - queue of packets from the network driver to be delivered to USB
+ */
+typedef struct
+{
+ struct list_head skb_list;
+ struct sk_buff *skb;
+ int state;
+ plusb_t *s;
+} skb_list_t,*pskb_list_t;
+
/* --------------------------------------------------------------------- */
#define NRPLUSB 4
+/*
+ * Interrupt endpoint status byte, from Prolific PL-2301 docs
+ * Check the 'download' link at www.prolifictech.com
+ */
+#define _PL_INT_RES1 0x80 /* reserved */
+#define _PL_INT_RES2 0x40 /* reserved */
+#define _PL_INT_RXD _PL_INT_RES2 /* Read data ready - Not documented by Prolific, but seems to work! */
+#define _PL_INT_TX_RDY 0x20 /* OK to transmit data */
+#define _PL_INT_RESET_O 0x10 /* reset output pipe */
+#define _PL_INT_RESET_I 0x08 /* reset input pipe */
+#define _PL_INT_TX_C 0x04 /* transmission complete */
+#define _PL_INT_TX_REQ 0x02 /* transmission received */
+#define _PL_INT_PEER_E 0x01 /* peer exists */
+
/*-------------------------------------------------------------------*/
static plusb_t plusb[NRPLUSB];
+static void plusb_write_bulk_complete(urb_t *purb);
+static void plusb_read_bulk_complete(urb_t *purb);
+static void plusb_int_complete(urb_t *purb);
+
/* --------------------------------------------------------------------- */
+
+/*
+ * plusb_add_buf_tail - Take the head of the src list and append it to
+ * the tail of the dest list
+ */
static int plusb_add_buf_tail (plusb_t *s, struct list_head *dst, struct list_head *src)
{
- unsigned long flags;
+ unsigned long flags = 0;
struct list_head *tmp;
int ret = 0;
}
/*-------------------------------------------------------------------*/
-static int plusb_my_bulk(plusb_t *s, int pipe, void *data, int size, int *actual_length)
+/*
+ * dequeue_next_skb - submit the first thing on the tx_skb_list to the
+ * USB stack. This function should be called each time we get a new
+ * message to send to the other host, or each time a message is successfully
+ * sent.
+ */
+static void dequeue_next_skb(char * func, plusb_t * s)
{
- int ret;
+ skb_list_t * skb_list;
+ unsigned long flags = 0;
- dbg("plusb_my_bulk: len:%d",size);
+ if (!s->connected)
+ return;
+
+ spin_lock_irqsave (&s->lock, flags);
+
+ if (!list_empty (&s->tx_skb_list) && !s->writeurb_submitted) {
+ int submit_ret;
+ skb_list = list_entry (s->tx_skb_list.next, skb_list_t, skb_list);
- ret=usb_bulk_msg(s->usbdev, pipe, data, size, actual_length, 500);
- if(ret<0) {
- err("plusb: usb_bulk_msg failed(%d)",ret);
+ if (skb_list->skb) {
+ s->writeurb_submitted = 1;
+
+ /* Use the buffer inside the sk_buff directly. why copy? */
+ FILL_BULK_URB_TO(s->writeurb, s->usbdev,
+ usb_sndbulkpipe(s->usbdev, _PLUSB_BULKOUTPIPE),
+ skb_list->skb->data, skb_list->skb->len,
+ plusb_write_bulk_complete, skb_list, 500);
+
+ dbg ("%s: %s: submitting urb. skb_list %p", s->net_dev.name, func, skb_list);
+
+ submit_ret = usb_submit_urb(s->writeurb);
+ if (submit_ret) {
+ s->writeurb_submitted = 0;
+ printk (KERN_CRIT "%s: %s: can't submit writeurb: %d\n",
+ s->net_dev.name, func, submit_ret);
+ }
+ } /* end if the skb value has been filled in */
}
- if( ret == -EPIPE ) {
- warn("CLEAR_FEATURE request to remove STALL condition.");
- if(usb_clear_halt(s->usbdev, usb_pipeendpoint(pipe)))
- err("request failed");
- }
-
- dbg("plusb_my_bulk: finished act: %d", *actual_length);
- return ret;
+ spin_unlock_irqrestore (&s->lock, flags);
}
-/* --------------------------------------------------------------------- */
-
-static void plusb_bh(void *context)
+/*
+ * submit_read_urb - re-submit the read URB to the stack
+ */
+void submit_read_urb(char * func, plusb_t * s)
{
- plusb_t *s=context;
- struct net_device_stats *stats=&s->net_stats;
- int ret=0;
- int actual_length;
- skb_list_t *skb_list;
- struct sk_buff *skb;
-
- dbg("plusb_bh: i:%d",in_interrupt());
-
- while(!list_empty(&s->tx_skb_list)) {
-
- if(!(s->status&_PLUSB_TXOK))
- break;
-
- skb_list = list_entry (s->tx_skb_list.next, skb_list_t, skb_list);
- if(!skb_list->state) {
- dbg("plusb_bh: not yet ready");
- schedule();
- continue;
- }
+ unsigned long flags=0;
- skb=skb_list->skb;
- ret=plusb_my_bulk(s, usb_sndbulkpipe (s->usbdev, _PLUSB_BULKOUTPIPE),
- skb->data, skb->len, &actual_length);
+ if (!s->connected)
+ return;
- if(ret || skb->len != actual_length ||!(skb->len%64)) {
- plusb_my_bulk(s, usb_sndbulkpipe (s->usbdev, _PLUSB_BULKOUTPIPE),
- NULL, 0, &actual_length);
- }
-
- if(!ret) {
- stats->tx_packets++;
- stats->tx_bytes+=skb->len;
- }
- else {
- stats->tx_errors++;
- stats->tx_aborted_errors++;
+ spin_lock_irqsave (&s->lock, flags);
+
+ if (!s->readurb_submitted) {
+ int ret;
+ s->readurb_submitted=1;
+ s->readurb->dev=s->usbdev;
+ ret = usb_submit_urb(s->readurb);
+ if (ret) {
+ printk (KERN_CRIT "%s: %s: error %d submitting read URB\n",
+ s->net_dev.name, func, ret);
+ s->readurb_submitted=0;
}
-
- dbg("plusb_bh: dev_kfree_skb");
-
- dev_kfree_skb(skb);
- skb_list->state=0;
- plusb_add_buf_tail (s, &s->free_skb_list, &s->tx_skb_list);
}
- dbg("plusb_bh: finished");
- s->in_bh=0;
+ spin_unlock_irqrestore (&s->lock, flags);
+
}
-
/* --------------------------------------------------------------------- */
+/*
+ * plusb_net_xmit - callback from the network device driver for outgoing data
+ *
+ * Data has arrived to the network device from the local machine and needs
+ * to be sent over the USB cable. This is in an interrupt, so we don't
+ * want to spend too much time in this function.
+ *
+ */
static int plusb_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
plusb_t *s=dev->priv;
skb_list_t *skb_list;
- int ret=NET_XMIT_SUCCESS;
+ unsigned int flags;
dbg("plusb_net_xmit: len:%d i:%d",skb->len,in_interrupt());
- if(!s->connected || list_empty(&s->free_skb_list)) {
- ret=NET_XMIT_CN;
- goto lab;
- }
+ if(!s->connected || !s->opened) {
+ /*
+ NOTE: If we get to this point, you'll return the error
+ kernel: virtual device plusb0 asks to queue packet
+
+ Other things we could do:
+ 1) just drop this packet
+ 2) drop other packets in the queue
+ */
+ return 1;
+ }
+
+ spin_lock_irqsave (&s->lock, flags);
- plusb_add_buf_tail (s, &s->tx_skb_list, &s->free_skb_list);
+ if (list_empty(&s->free_skb_list)
+ || plusb_add_buf_tail (s, &s->tx_skb_list, &s->free_skb_list)) {
+ /* The buffers on this side are full. DROP the packet
+ I think that this shouldn't happen with the correct
+ use of the netif_XXX functions -EZA
+ */
+ dbg ("plusb: Free list is empty.");
+ kfree_skb(skb);
+ s->net_stats.tx_dropped++;
+ spin_unlock_irqrestore (&s->lock, flags);
+ return 0;
+ }
+
skb_list = list_entry (s->tx_skb_list.prev, skb_list_t, skb_list);
skb_list->skb=skb;
skb_list->state=1;
+ skb_list->s=s;
-lab:
- if(s->in_bh)
- return ret;
+ if (list_empty(&s->free_skb_list)) {
+ /* apply "backpressure". Tell the net layer to stop sending
+ the driver packets.
+ */
+ netif_stop_queue(dev);
+ }
+
+ spin_unlock_irqrestore (&s->lock, flags);
+
+ /* If there is no write urb outstanding, pull the first thing
+ off of the list and submit it to the USB stack
+ */
+ dequeue_next_skb("plusb_net_xmit", s);
+
+ return 0;
+}
- dbg("plusb_net_xmit: queue_task");
+/* --------------------------------------------------------------------- */
- s->in_bh=1;
- queue_task(&s->bh, &tq_scheduler);
+/*
+ * plusb_write_bulk_complete () - callback after the data has been
+ * sent to the USB device, or a timeout occurred.
+ */
+static void plusb_write_bulk_complete(urb_t *purb)
+{
+ skb_list_t * skb_list=purb->context;
+ plusb_t *s=skb_list->s;
- dbg("plusb_net_xmit: finished");
- return ret;
+ dbg ("%s: plusb_write_bulk_complete: status:%d skb_list:%p\n",
+ s->net_dev.name, purb->status, skb_list);
-}
+ skb_list->state=0;
-/* --------------------------------------------------------------------- */
+ if( purb->status == -EPIPE )
+ printk(KERN_CRIT "%s: plusb_write_bulk_complete: got -EPIPE and don't know what to do!\n",
+ s->net_dev.name);
+
+ if(!purb->status) {
+ s->net_stats.tx_packets++;
+ s->net_stats.tx_bytes+=skb_list->skb->len;
+ }
+ else {
+ err ("%s: plusb_write_bulk_complete: returned ERROR status:%d\n",
+ s->net_dev.name, purb->status);
+
+ s->net_stats.tx_errors++;
+ s->net_stats.tx_aborted_errors++;
+ }
+
+ dbg("plusb_bh: dev_kfree_skb");
+
+ /* NOTE: In 2.4 it's a problem to call dev_kfree_skb() in a hard IRQ:
+ Oct 28 23:42:14 bug kernel: Warning: kfree_skb on hard IRQ c023329a
+ */
+ dev_kfree_skb_any(skb_list->skb);
+
+ skb_list->skb = NULL;
+ if (plusb_add_buf_tail (s, &s->free_skb_list, &s->tx_skb_list)) {
+ err ("plusb: tx list empty. This shouldn't happen.");
+ }
+
+ purb->status = 0;
+ s->writeurb_submitted = 0;
+
+ netif_wake_queue((&s->net_dev));
+
+ dequeue_next_skb("plusb_write_bulk_complete", s);
-static void plusb_bulk_complete(urb_t *purb)
+
+}
+
+/*
+ * plusb_read_bulk_complete - Callback for data arriving from the USB device
+ *
+ * This gets called back when a full 'urb' is received from the remote system.
+ * This urb was allocated by this driver and is kept in the member: s->readurb
+ *
+ */
+static void plusb_read_bulk_complete(urb_t *purb)
{
+
plusb_t *s=purb->context;
- dbg("plusb_bulk_complete: status:%d length:%d",purb->status,purb->actual_length);
+ dbg("plusb_read_bulk_complete: status:%d length:%d", purb->status,purb->actual_length);
+
if(!s->connected)
return;
- if( !purb->status) {
+ if( purb->status == -EPIPE )
+ printk(KERN_CRIT "%s: plusb_read_bulk_complete: got -EPIPE and I don't know what to do!\n",
+ s->net_dev.name);
+ else if (!purb->status) {
struct sk_buff *skb;
unsigned char *dst;
int len=purb->transfer_buffer_length;
skb=dev_alloc_skb(len);
if(!skb) {
- err("plusb_bulk_complete: dev_alloc_skb(%d)=NULL, dropping frame",len);
+ printk (KERN_CRIT "%s: plusb_read_bulk_complete: dev_alloc_skb(%d)=NULL, dropping frame\n", s->net_dev.name, len);
stats->rx_dropped++;
- return;
+ } else {
+ dst=(char *)skb_put(skb, len);
+ memcpy( dst, purb->transfer_buffer, len);
+
+ skb->dev=&s->net_dev;
+ skb->protocol=eth_type_trans(skb, skb->dev);
+ stats->rx_packets++;
+ stats->rx_bytes+=len;
+ netif_rx(skb);
}
+
+ }
+
+ s->readurb_submitted = 0;
+
+ if (purb->status) {
+ /* Give the system a chance to "catch its breath". Shortcut
+ re-submitting the read URB> It will be re-submitted if
+ another interrupt comes back. The problem scenario is that
+ the plub is pulled and the read returns an error.
+ You don't want to resumbit in this case.
+ */
+ err ("%s: plusb_read_bulk_complete: returned status %d\n",
+ s->net_dev.name, purb->status);
+ return;
+ }
- dst=(char *)skb_put(skb, len);
- memcpy( dst, purb->transfer_buffer, len);
- skb->dev=&s->net_dev;
- skb->protocol=eth_type_trans(skb, skb->dev);
- stats->rx_packets++;
- stats->rx_bytes+=len;
- netif_rx(skb);
- }
- else
- purb->status=0;
+ purb->status=0;
+
+ /* Keep it coming! resubmit the URB for reading.. Make sure
+ we aren't in contention with the interrupt callback.
+ */
+ submit_read_urb("plusb_read_bulk_complete", s);
}
/* --------------------------------------------------------------------- */
-
+/*
+ * plusb_int_complete - USB driver callback for interrupt msg from the device
+ *
+ * Interrupts are scheduled to go off on a periodic basis (see FILL_INT_URB)
+ * For the prolific device, this is basically just returning a register
+ * filled with bits. See the macro definitions for _PL_INT_XXX above.
+ * Most of these bits are for implementing a machine-machine protocol
+ * and can be set with a special message (described as the "Quicklink"
+ * feature in the prolific documentation.)
+ *
+ * I don't think we need any of that to work as a network device. If a
+ * message is lost, big deal - that's what UNIX networking expects from
+ * the physical layer.
+ *
+ */
static void plusb_int_complete(urb_t *purb)
{
plusb_t *s=purb->context;
s->status=((unsigned char*)purb->transfer_buffer)[0]&255;
+
#if 0
+ /* This isn't right because 0x20 is TX_RDY and
+ sometimes will not be set
+ */
if((s->status&0x3f)!=0x20) {
warn("invalid device status %02X", s->status);
return;
if(!s->connected)
return;
- if(s->status&_PLUSB_RXD) {
- int ret;
-
- if(s->bulkurb->status) {
- err("plusb_int_complete: URB still in use");
- return;
- }
-
- ret=usb_submit_urb(s->bulkurb);
- if(ret && ret!=-EBUSY) {
- err("plusb_int_complete: usb_submit_urb failed");
- }
- }
-
- if(purb->status || s->status!=160)
- dbg("status: %p %d buf: %02X", purb->dev, purb->status, s->status);
+ /* Don't turn this on unless you want to see the log flooded. */
+#if 0
+ printk("plusb_int_complete: PEER_E:%d TX_REQ:%d TX_C:%d RESET_IN:%d RESET_O: %d TX_RDY:%d RES1:%d RES2:%d\n",
+ s->status & _PL_INT_PEER_E ? 1 : 0,
+ s->status & _PL_INT_TX_REQ ? 1 : 0,
+ s->status & _PL_INT_TX_C ? 1 : 0,
+ s->status & _PL_INT_RESET_I ? 1 : 0,
+ s->status & _PL_INT_RESET_O ? 1 : 0,
+ s->status & _PL_INT_TX_RDY ? 1 : 0,
+ s->status & _PL_INT_RES1 ? 1 : 0,
+ s->status & _PL_INT_RES2 ? 1 : 0);
+#endif
+
+#if 1
+ /* At first glance, this logic appears to not really be needed, but
+ it can help recover from intermittent problems where the
+ usb_submit_urb() fails in the read callback. -EZA
+ */
+
+ /* Try to submit the read URB again. Make sure
+ we aren't in contention with the bulk read callback
+ */
+ submit_read_urb ("plusb_int_complete", s);
+
+ /* While we are at it, why not check to see if the
+ write urb should be re-submitted?
+ */
+ dequeue_next_skb("plusb_int_complete", s);
+
+#endif
+
}
/* --------------------------------------------------------------------- */
-
+/*
+ * plusb_free_all - deallocate all memory kept for an instance of the device.
+ */
static void plusb_free_all(plusb_t *s)
{
struct list_head *skb;
skb_list_t *skb_list;
dbg("plusb_free_all");
+
+ /* set a flag to tell all callbacks to cease and desist */
+ s->connected = 0;
+
+ /* If the interrupt handler is about to fire, let it finish up */
run_task_queue(&tq_immediate);
if(s->inturb) {
dbg("unlink inturb");
usb_unlink_urb(s->inturb);
- }
-
- if(s->inturb && s->inturb->transfer_buffer) {
- dbg("kfree inturb->transfer_buffer");
- kfree(s->inturb->transfer_buffer);
- s->inturb->transfer_buffer=NULL;
- }
-
- if(s->inturb) {
dbg("free_urb inturb");
usb_free_urb(s->inturb);
s->inturb=NULL;
}
+
+ if(s->interrupt_in_buffer) {
+ dbg("kfree s->interrupt_in_buffer");
+ kfree(s->interrupt_in_buffer);
+ s->interrupt_in_buffer=NULL;
+ }
- if(s->bulkurb) {
- dbg("unlink bulkurb");
- usb_unlink_urb(s->bulkurb);
+ if(s->readurb) {
+ dbg("unlink readurb");
+ usb_unlink_urb(s->readurb);
+ dbg("free_urb readurb:");
+ usb_free_urb(s->readurb);
+ s->readurb=NULL;
}
-
- if(s->bulkurb && s->bulkurb->transfer_buffer) {
- dbg("kfree bulkurb->transfer_buffer");
- kfree(s->bulkurb->transfer_buffer);
- s->bulkurb->transfer_buffer=NULL;
+
+ if(s->bulk_in_buffer) {
+ dbg("kfree s->bulk_in_buffer");
+ kfree(s->bulk_in_buffer);
+ s->bulk_in_buffer=NULL;
}
- if(s->bulkurb) {
- dbg("free_urb bulkurb");
- usb_free_urb(s->bulkurb);
- s->bulkurb=NULL;
+
+ s->readurb_submitted = 0;
+
+ if(s->writeurb) {
+ dbg("unlink writeurb");
+ usb_unlink_urb(s->writeurb);
+ dbg("free_urb writeurb:");
+ usb_free_urb(s->writeurb);
+ s->writeurb=NULL;
}
+
+ s->writeurb_submitted = 0;
while(!list_empty(&s->free_skb_list)) {
skb=s->free_skb_list.next;
skb=s->tx_skb_list.next;
list_del(skb);
skb_list = list_entry (skb, skb_list_t, skb_list);
- kfree(skb_list);
+ if (skb_list->skb) {
+ dbg ("Freeing SKB in queue");
+ dev_kfree_skb_any(skb_list->skb);
+ skb_list->skb = NULL;
+ }
+ kfree(skb_list);
}
+
+ s->in_bh=0;
+
dbg("plusb_free_all: finished");
}
/*-------------------------------------------------------------------*/
-
+/*
+ * plusb_alloc - allocate memory associated with one instance of the device
+ */
static int plusb_alloc(plusb_t *s)
{
int i;
skb_list_t *skb;
dbg("plusb_alloc");
-
+
for(i=0 ; i < _SKB_NUM ; i++) {
skb=kmalloc(sizeof(skb_list_t), GFP_KERNEL);
if(!skb) {
goto reject;
}
- dbg("bulkurb allocation:");
- s->bulkurb=usb_alloc_urb(0);
- if(!s->bulkurb) {
+ dbg("bulk read urb allocation:");
+ s->readurb=usb_alloc_urb(0);
+ if(!s->readurb) {
err("alloc_urb failed");
goto reject;
}
- dbg("bulkurb/inturb init:");
- s->inturb->dev=s->usbdev;
- s->inturb->pipe=usb_rcvintpipe (s->usbdev, _PLUSB_INTPIPE);
- s->inturb->transfer_buffer=kmalloc(64, GFP_KERNEL);
- if(!s->inturb->transfer_buffer) {
- err("kmalloc failed");
+ dbg("bulk write urb allocation:");
+ s->writeurb=usb_alloc_urb(0);
+ if(!s->writeurb) {
+ err("alloc_urb for writeurb failed");
goto reject;
}
- s->inturb->transfer_buffer_length=1;
- s->inturb->complete=plusb_int_complete;
- s->inturb->context=s;
- s->inturb->interval=10;
+ dbg("readurb/inturb init:");
+ s->interrupt_in_buffer=kmalloc(64, GFP_KERNEL);
+ if(!s->interrupt_in_buffer) {
+ err("kmalloc failed");
+ goto reject;
+ }
+
+ /* The original value of '10' makes this interrupt fire off a LOT.
+ It was set so low because the callback determined when to
+ sumbit the buld read URB. I've lowered it to 100 - the driver
+ doesn't depend on that logic anymore. -EZA
+ */
+ FILL_INT_URB(s->inturb, s->usbdev,
+ usb_rcvintpipe (s->usbdev, _PLUSB_INTPIPE),
+ s->interrupt_in_buffer, 1,
+ plusb_int_complete, s, HZ);
dbg("inturb submission:");
if(usb_submit_urb(s->inturb)<0) {
err("usb_submit_urb failed");
goto reject;
}
-
- dbg("bulkurb init:");
- s->bulkurb->dev=s->usbdev;
- s->bulkurb->pipe=usb_rcvbulkpipe (s->usbdev, _PLUSB_BULKINPIPE);
- s->bulkurb->transfer_buffer=kmalloc(_BULK_DATA_LEN, GFP_KERNEL);
- if(!s->bulkurb->transfer_buffer) {
- err("kmalloc failed");
- goto reject;
- }
- s->bulkurb->transfer_buffer_length=_BULK_DATA_LEN;
- s->bulkurb->complete=plusb_bulk_complete;
- s->bulkurb->context=s;
-
- dbg("plusb_alloc: finished");
+ dbg("readurb init:");
+ s->bulk_in_buffer = kmalloc(_BULK_DATA_LEN, GFP_KERNEL);
+ if (!s->bulk_in_buffer) {
+ err("kmalloc %d bytes for bulk in buffer failed", _BULK_DATA_LEN);
+ }
+
+ FILL_BULK_URB(s->readurb, s->usbdev,
+ usb_rcvbulkpipe(s->usbdev, _PLUSB_BULKINPIPE),
+ s->bulk_in_buffer, _BULK_DATA_LEN,
+ plusb_read_bulk_complete, s);
+
+ /* The write urb will be initialized inside the network
+ interrupt.
+ */
+
+ /* get the bulk read going */
+ submit_read_urb("plusb_alloc", s);
+
+ dbg ("plusb_alloc: finished. readurb=%p writeurb=%p inturb=%p",
+ s->readurb, s->writeurb, s->inturb);
return 0;
return -ENOMEM;
s->opened=1;
- MOD_INC_USE_COUNT;
+ MOD_INC_USE_COUNT;
+
+ netif_start_queue(dev);
+
dbg("plusb_net_open: success");
return 0;
static int plusb_net_stop(struct net_device *dev)
{
plusb_t *s=dev->priv;
+
+ netif_stop_queue(dev);
dbg("plusb_net_stop");
- plusb_free_all(s);
s->opened=0;
+ plusb_free_all(s);
+
MOD_DEC_USE_COUNT;
dbg("plusb_net_stop:finished");
return 0;
{
plusb_t *s = ptr;
- printk ("plusb_net_disconnect: Starting\n");
-
dbg("plusb_disconnect");
- s->connected = 0;
-
plusb_free_all(s);
if(!s->opened && s->net_dev.name) {
dbg("unregistering netdev: %s",s->net_dev.name);
unregister_netdev(&s->net_dev);
s->net_dev.name[0] = '\0';
-#if (LINUX_VERSION_CODE < 0x020300)
- kfree (s->net_dev.name);
- s->net_dev.name = NULL;
-#endif
+#if (LINUX_VERSION_CODE < 0x020300)
+ dbg("plusb_disconnect: About to free name");
+ kfree (s->net_dev.name);
+ s->net_dev.name = NULL;
+#endif
}
dbg("plusb_disconnect: finished");
-
- printk ("plusb_net_disconnect: Finished\n");
-
MOD_DEC_USE_COUNT;
}
/* --------------------------------------------------------------------- */
+static int plusb_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if ((new_mtu < 68) || (new_mtu > _BULK_DATA_LEN))
+ return -EINVAL;
+
+ printk("plusb: changing mtu to %d\n", new_mtu);
+ dev->mtu = new_mtu;
+
+ /* NOTE: Could we change the size of the READ URB here dynamically
+ to save kernel memory?
+ */
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
int plusb_net_init(struct net_device *dev)
{
dbg("plusb_net_init");
dev->hard_start_xmit=plusb_net_xmit;
dev->get_stats = plusb_net_get_stats;
ether_setup(dev);
- dev->tx_queue_len = 0;
+ dev->change_mtu = plusb_change_mtu;
+ /* Setting the default MTU to 16K gives good performance for
+ me, and keeps the ping latency low too. Setting it up
+ to 32K made performance go down. -EZA
+ Pavel says it would be best not to do this...
+ */
+ /*dev->mtu=16384; */
+ dev->tx_queue_len = 0;
dev->flags = IFF_POINTOPOINT|IFF_NOARP;
{
plusb_t *s;
- printk ("plusb_probe: Starting\n");
-
- if (usbdev) {
- printk("plusb: probe: vendor id 0x%x, device id 0x%x ifnum:%d\n",
- usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, ifnum);
- } else {
- printk ("plusb: usbdev is NULL!\n");
- }
+ dbg("plusb: probe: vendor id 0x%x, device id 0x%x ifnum:%d",
+ usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, ifnum);
if (usbdev->descriptor.idVendor != 0x067b || usbdev->descriptor.idProduct > 0x1)
return NULL;
if (usbdev->descriptor.bNumConfigurations != 1)
return NULL;
- printk ("plusb_probe: Looking for Struct\n");
s = plusb_find_struct ();
if (!s)
return NULL;
s->usbdev = usbdev;
- printk ("plusb_probe: Setting Configuration\n");
if (usb_set_configuration (s->usbdev, usbdev->config[0].bConfigurationValue) < 0) {
err("set_configuration failed");
return NULL;
}
- printk ("plusb_probe: Setting Interface\n");
if (usb_set_interface (s->usbdev, 0, 0) < 0) {
err("set_interface failed");
return NULL;
}
- printk ("plusb_probe: Checking device name\n");
-
#if (LINUX_VERSION_CODE < 0x020300)
- {
- int i;
+ {
+ int i;
- /* EZA: find the device number... we seem to have lost it...*/
- for (i=0; i<NRPLUSB; i++) {
- if (&plusb[i] == s)
- break;
- }
-
- /* EZA: for Kernel version 2.2, the driver is responsible for
- allocating this memory. For version 2.4, the rules
- have apparently changed, but there is a nifty function
- 'init_netdev' that might make this easier... It's in
- ../net/net_init.c - but can we get there from here? (no)
- */
- if(!s->net_dev.name) {
- s->net_dev.name = kmalloc(strlen("plusbXXXX"), GFP_KERNEL);
- sprintf (s->net_dev.name, "plusb%d", i);
- s->net_dev.init=plusb_net_init;
- s->net_dev.priv=s;
-
- printk ("plusb_probe: Registering Device\n");
- if(!register_netdev(&s->net_dev))
- info("registered: %s", s->net_dev.name);
- else {
- err("register_netdev failed");
- s->net_dev.name[0] = '\0';
- }
- printk ("plusb_probe: Connected!\n");
- }
- }
+ /* For Kernel version 2.2, the driver is responsible for
+ allocating this memory. For version 2.4, the rules
+ have apparently changed, but there is a nifty function
+ 'init_netdev' that might make this easier... It's in
+ ../net/net_init.c - but can we get there from here? (no)
+ -EZA
+ */
+
+ /* Find the device number... we seem to have lost it... -EZA */
+ for (i=0; i<NRPLUSB; i++) {
+ if (&plusb[i] == s)
+ break;
+ }
+
+ if(!s->net_dev.name) {
+ s->net_dev.name = kmalloc(strlen("plusbXXXX"), GFP_KERNEL);
+ sprintf (s->net_dev.name, "plusb%d", i);
+ s->net_dev.init=plusb_net_init;
+ s->net_dev.priv=s;
+
+ printk ("plusb_probe: Registering Device\n");
+ if(!register_netdev(&s->net_dev))
+ info("registered: %s", s->net_dev.name);
+ else {
+ err("register_netdev failed");
+ s->net_dev.name[0] = '\0';
+ }
+ dbg ("plusb_probe: Connected!");
+ }
+ }
#else
- /* Kernel version 2.3+ works a little bit differently */
+ /* Kernel version 2.3+ works a little bit differently than 2.2 */
if(!s->net_dev.name[0]) {
strcpy(s->net_dev.name, "plusb%d");
s->net_dev.init=plusb_net_init;
s->net_dev.name[0] = '\0';
}
}
-
#endif
+
s->connected = 1;
- printk ("plusb_probe: Set Connected\n");
if(s->opened) {
dbg("net device already allocated, restarting USB transfers");
static int __init plusb_init (void)
{
unsigned u;
-
dbg("plusb_init");
/* initialize struct */
for (u = 0; u < NRPLUSB; u++) {
- plusb_t *s;
- dbg("plusb_init: u=%ud about to assign s\n", u);
- s= &plusb[u];
- dbg("plusb_init: u=%ud about to memset\n", u);
+ plusb_t *s = &plusb[u];
memset (s, 0, sizeof (plusb_t));
- s->bh.routine = (void (*)(void *))plusb_bh;
- s->bh.data = s;
- dbg("plusb_init: u=%ud about to init list head\n", u);
INIT_LIST_HEAD (&s->tx_skb_list);
INIT_LIST_HEAD (&s->free_skb_list);
- dbg("plusb_init: u=%ud about to init spin lock\n", u);
spin_lock_init (&s->lock);
}
-
- dbg ("plusb_init: Initialized structure\n");
-
+
/* register misc device */
usb_register (&plusb_driver);
dbg("plusb_init: driver registered");
+ info(DRIVER_VERSION ":" DRIVER_DESC);
+
return 0;
}
dbg("plusb_cleanup");
for (u = 0; u < NRPLUSB; u++) {
plusb_t *s = &plusb[u];
+#if (LINUX_VERSION_CODE < 0x020300)
+ if(s->net_dev.name) {
+ dbg("unregistering netdev: %s",s->net_dev.name);
+ unregister_netdev(&s->net_dev);
+ s->net_dev.name[0] = '\0';
+ kfree (s->net_dev.name);
+ s->net_dev.name = NULL;
+ }
+#else
if(s->net_dev.name[0]) {
dbg("unregistering netdev: %s",s->net_dev.name);
unregister_netdev(&s->net_dev);
+ s->net_dev.name[0] = '\0';
}
+#endif
}
usb_deregister (&plusb_driver);
dbg("plusb_cleanup: finished");
/* --------------------------------------------------------------------- */
-MODULE_AUTHOR ("Deti Fliegl, deti@fliegl.de");
-MODULE_DESCRIPTION ("PL-2302 USB Interface Driver for Linux (c)2000");
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
module_init (plusb_init);
return -1;
info("USB Rio support registered.");
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
usb_serial_register (&belkin_old_device);
usb_serial_register (&peracom_device);
usb_serial_register (&gocom232_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
{
usb_serial_register (&digi_acceleport_2_device);
usb_serial_register (&digi_acceleport_4_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
*
* See Documentation/usb/usb-serial.txt for more information on using this driver
*
+ * (07/29/2001) gb
+ * remove unused code in empeg_close() (thanks to Oliver Neukum for pointing this
+ * out) and rewrote empeg_set_termios().
+ *
* (04/08/2001) gb
* Identify version on module load.
*
/*
* Version Information
*/
-#define DRIVER_VERSION "v1.0.0"
+#define DRIVER_VERSION "v1.0.1"
#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Gary Brubaker <xavyer@ix.netcom.com>"
#define DRIVER_DESC "USB Empeg Mark I/II Driver"
if (!port->active) {
- /* gb - 2000/11/05
- * personally, I think these termios should be set in
- * empeg_startup(), but it appears doing so leads to one
- * of those chicken/egg problems. :)
- */
- port->tty->termios->c_iflag
- &= ~(IGNBRK
- | BRKINT
- | PARMRK
- | ISTRIP
- | INLCR
- | IGNCR
- | ICRNL
- | IXON);
-
- port->tty->termios->c_oflag
- &= ~OPOST;
-
- port->tty->termios->c_lflag
- &= ~(ECHO
- | ECHONL
- | ICANON
- | ISIG
- | IEXTEN);
-
- port->tty->termios->c_cflag
- &= ~(CSIZE
- | PARENB);
-
- port->tty->termios->c_cflag
- |= CS8;
-
- /* gb - 2000/12/03
- * Contributed by Borislav Deianov
- * Notify the tty driver that the termios have changed!!
- */
- port->tty->ldisc.set_termios(port->tty, NULL);
-
- /* gb - 2000/11/05
- * force low_latency on
- *
- * The tty_flip_buffer_push()'s in empeg_read_bulk_callback() will actually
- * force the data through if low_latency is set. Otherwise the pushes are
- * scheduled; this is bad as it opens up the possibility of dropping bytes
- * on the floor. We are trying to sustain high data transfer rates; and
- * don't want to drop bytes on the floor.
- * Moral: use low_latency - drop no bytes - life is good. :)
- */
- port->tty->low_latency = 1;
+ /* Force default termio settings */
+ empeg_set_termios (port, NULL) ;
port->active = 1;
bytes_in = 0;
static void empeg_close (struct usb_serial_port *port, struct file * filp)
{
struct usb_serial *serial;
- unsigned char *transfer_buffer;
unsigned long flags;
if (port_paranoia_check (port, __FUNCTION__))
--port->open_count;
if (port->open_count <= 0) {
- transfer_buffer = kmalloc (0x12, GFP_KERNEL);
-
- if (!transfer_buffer) {
- err(__FUNCTION__ " - kmalloc(%d) failed.", 0x12);
- } else {
- kfree (transfer_buffer);
- }
-
/* shutdown our bulk read */
usb_unlink_urb (port->read_urb);
port->active = 0;
}
-/* This function is all nice and good, but we don't change anything based on it :) */
static void empeg_set_termios (struct usb_serial_port *port, struct termios *old_termios)
{
- unsigned int cflag = port->tty->termios->c_cflag;
dbg(__FUNCTION__ " - port %d", port->number);
- /* check that they really want us to change something */
- if (old_termios) {
- if ((cflag == old_termios->c_cflag) &&
- (RELEVANT_IFLAG(port->tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) {
- dbg(__FUNCTION__ " - nothing to change...");
- return;
- }
- }
-
if ((!port->tty) || (!port->tty->termios)) {
dbg(__FUNCTION__" - no tty structures");
return;
}
- /* get the byte size */
- switch (cflag & CSIZE) {
- case CS5: dbg(__FUNCTION__ " - data bits = 5"); break;
- case CS6: dbg(__FUNCTION__ " - data bits = 6"); break;
- case CS7: dbg(__FUNCTION__ " - data bits = 7"); break;
- default:
- case CS8: dbg(__FUNCTION__ " - data bits = 8"); break;
- }
-
- /* determine the parity */
- if (cflag & PARENB)
- if (cflag & PARODD)
- dbg(__FUNCTION__ " - parity = odd");
- else
- dbg(__FUNCTION__ " - parity = even");
- else
- dbg(__FUNCTION__ " - parity = none");
-
- /* figure out the stop bits requested */
- if (cflag & CSTOPB)
- dbg(__FUNCTION__ " - stop bits = 2");
- else
- dbg(__FUNCTION__ " - stop bits = 1");
-
- /* figure out the flow control settings */
- if (cflag & CRTSCTS)
- dbg(__FUNCTION__ " - RTS/CTS is enabled");
- else
- dbg(__FUNCTION__ " - RTS/CTS is disabled");
-
- /* determine software flow control */
- if (I_IXOFF(port->tty))
- dbg(__FUNCTION__ " - XON/XOFF is enabled, XON = %2x, XOFF = %2x", START_CHAR(port->tty), STOP_CHAR(port->tty));
- else
- dbg(__FUNCTION__ " - XON/XOFF is disabled");
-
- /* get the baud rate wanted */
- dbg(__FUNCTION__ " - baud rate = %d", tty_get_baud_rate(port->tty));
+ /*
+ * The empeg-car player wants these particular tty settings.
+ * You could, for example, change the baud rate, however the
+ * player only supports 115200 (currently), so there is really
+ * no point in support for changes to the tty settings.
+ * (at least for now)
+ *
+ * The default requirements for this device are:
+ */
+ port->tty->termios->c_iflag
+ &= ~(IGNBRK /* disable ignore break */
+ | BRKINT /* disable break causes interrupt */
+ | PARMRK /* disable mark parity errors */
+ | ISTRIP /* disable clear high bit of input characters */
+ | INLCR /* disable translate NL to CR */
+ | IGNCR /* disable ignore CR */
+ | ICRNL /* disable translate CR to NL */
+ | IXON); /* disable enable XON/XOFF flow control */
+
+ port->tty->termios->c_oflag
+ &= ~OPOST; /* disable postprocess output characters */
+
+ port->tty->termios->c_lflag
+ &= ~(ECHO /* disable echo input characters */
+ | ECHONL /* disable echo new line */
+ | ICANON /* disable erase, kill, werase, and rprnt special characters */
+ | ISIG /* disable interrupt, quit, and suspend special characters */
+ | IEXTEN); /* disable non-POSIX special characters */
+
+ port->tty->termios->c_cflag
+ &= ~(CSIZE /* no size */
+ | PARENB /* disable parity bit */
+ | CBAUD); /* clear current baud rate */
+
+ port->tty->termios->c_cflag
+ |= (CS8 /* character size 8 bits */
+ | B115200); /* baud rate 115200 */
+
+ /*
+ * Force low_latency on; otherwise the pushes are scheduled;
+ * this is bad as it opens up the possibility of dropping bytes
+ * on the floor. We don't want to drop bytes on the floor. :)
+ */
+ port->tty->low_latency = 1;
+
+ /* Notify the tty driver that the termios have changed. */
+ port->tty->ldisc.set_termios(port->tty, NULL);
return;
}
}
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
dbg(__FUNCTION__);
usb_serial_register (&ftdi_sio_device);
usb_serial_register (&ftdi_8U232AM_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
usb_serial_register (&edgeport_16dual_device);
usb_serial_register (&edgeport_compat_id_device);
usb_serial_register (&edgeport_8i_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
usb_serial_register (&keyspan_usa28x_device);
usb_serial_register (&keyspan_usa49w_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
{
usb_serial_register (&keyspan_pda_fake_device);
usb_serial_register (&keyspan_pda_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
usb_serial_register (&mct_u232_device);
usb_serial_register (&mct_u232_sitecom_device);
usb_serial_register (&mct_u232_du_h3sp_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
static int __init omninet_init (void)
{
usb_serial_register (&zyxel_omninet_device);
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
return -1;
}
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
}
}
- info(DRIVER_VERSION " " DRIVER_AUTHOR);
- info(DRIVER_DESC);
+ info(DRIVER_VERSION ":" DRIVER_DESC);
return 0;
}
/*
* USB ConnectTech WhiteHEAT driver
*
- * Copyright (C) 1999, 2000
+ * Copyright (C) 1999 - 2001
* Greg Kroah-Hartman (greg@kroah.com)
*
* This program is free software; you can redistribute it and/or modify
* (at your option) any later version.
*
* See Documentation/usb/usb-serial.txt for more information on using this driver
+ *
+ * (04/08/2001) gb
+ * Identify version on module load.
+ *
+ * 2001_Mar_19 gkh
+ * Fixed MOD_INC and MOD_DEC logic, the ability to open a port more
+ * than once, and the got the proper usb_device_id table entries so
+ * the driver works again.
*
* (10/05/2000) gkh
* Fixed bug with urb->dev not being set properly, now that the usb
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
+#include <linux/usb.h>
#ifdef CONFIG_USB_SERIAL_DEBUG
- #define DEBUG
+ static int debug = 1;
#else
- #undef DEBUG
+ static int debug;
#endif
-#include <linux/usb.h>
-
-static int debug;
#include "usb-serial.h"
-
#include "whiteheat_fw.h" /* firmware for the ConnectTech WhiteHEAT device */
-
#include "whiteheat.h" /* WhiteHEAT specific commands */
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v1.0.0"
+#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
+#define DRIVER_DESC "USB ConnectTech WhiteHEAT driver"
+
#define CONNECT_TECH_VENDOR_ID 0x0710
#define CONNECT_TECH_FAKE_WHITE_HEAT_ID 0x0001
#define CONNECT_TECH_WHITE_HEAT_ID 0x8001
dbg(__FUNCTION__" - port %d", port->number);
- if (port->active) {
- dbg (__FUNCTION__ " - device already open");
- return -EINVAL;
- }
- port->active = 1;
-
- /* set up some stuff for our command port */
- command_port = &port->serial->port[COMMAND_PORT];
- if (command_port->private == NULL) {
- info = (struct whiteheat_private *)kmalloc (sizeof(struct whiteheat_private), GFP_KERNEL);
- if (info == NULL) {
- err(__FUNCTION__ " - out of memory");
- return -ENOMEM;
+ ++port->open_count;
+ MOD_INC_USE_COUNT;
+
+ if (!port->active) {
+ port->active = 1;
+
+ /* set up some stuff for our command port */
+ command_port = &port->serial->port[COMMAND_PORT];
+ if (command_port->private == NULL) {
+ info = (struct whiteheat_private *)kmalloc (sizeof(struct whiteheat_private), GFP_KERNEL);
+ if (info == NULL) {
+ err(__FUNCTION__ " - out of memory");
+ return -ENOMEM;
+ }
+
+ init_waitqueue_head(&info->wait_command);
+ command_port->private = info;
+ command_port->write_urb->complete = command_port_write_callback;
+ command_port->read_urb->complete = command_port_read_callback;
+ command_port->read_urb->dev = port->serial->dev;
+ command_port->tty = port->tty; /* need this to "fake" our our sanity check macros */
+ usb_submit_urb (command_port->read_urb);
}
- init_waitqueue_head(&info->wait_command);
- command_port->private = info;
- command_port->write_urb->complete = command_port_write_callback;
- command_port->read_urb->complete = command_port_read_callback;
- command_port->read_urb->dev = port->serial->dev;
- command_port->tty = port->tty; /* need this to "fake" our our sanity check macros */
- usb_submit_urb (command_port->read_urb);
- }
+ /* Start reading from the device */
+ port->read_urb->dev = port->serial->dev;
+ result = usb_submit_urb(port->read_urb);
+ if (result)
+ err(__FUNCTION__ " - failed submitting read urb, error %d", result);
- /* Start reading from the device */
- port->read_urb->dev = port->serial->dev;
- result = usb_submit_urb(port->read_urb);
- if (result)
- err(__FUNCTION__ " - failed submitting read urb, error %d", result);
-
- /* send an open port command */
- /* firmware uses 1 based port numbering */
- open_command.port = port->number - port->serial->minor + 1;
- whiteheat_send_cmd (port->serial, WHITEHEAT_OPEN, (__u8 *)&open_command, sizeof(open_command));
-
- /* Need to do device specific setup here (control lines, baud rate, etc.) */
- /* FIXME!!! */
+ /* send an open port command */
+ /* firmware uses 1 based port numbering */
+ open_command.port = port->number - port->serial->minor + 1;
+ whiteheat_send_cmd (port->serial, WHITEHEAT_OPEN, (__u8 *)&open_command, sizeof(open_command));
+
+ /* Need to do device specific setup here (control lines, baud rate, etc.) */
+ /* FIXME!!! */
+ }
dbg(__FUNCTION__ " - exit");
dbg(__FUNCTION__ " - port %d", port->number);
- /* send a close command to the port */
- /* firmware uses 1 based port numbering */
- close_command.port = port->number - port->serial->minor + 1;
- whiteheat_send_cmd (port->serial, WHITEHEAT_CLOSE, (__u8 *)&close_command, sizeof(close_command));
+ --port->open_count;
- /* Need to change the control lines here */
- /* FIXME */
+ if (port->open_count <= 0) {
+ /* send a close command to the port */
+ /* firmware uses 1 based port numbering */
+ close_command.port = port->number - port->serial->minor + 1;
+ whiteheat_send_cmd (port->serial, WHITEHEAT_CLOSE, (__u8 *)&close_command, sizeof(close_command));
- /* shutdown our bulk reads and writes */
- usb_unlink_urb (port->write_urb);
- usb_unlink_urb (port->read_urb);
- port->active = 0;
+ /* Need to change the control lines here */
+ /* FIXME */
+
+ /* shutdown our bulk reads and writes */
+ usb_unlink_urb (port->write_urb);
+ usb_unlink_urb (port->read_urb);
+ port->active = 0;
+ }
+ MOD_DEC_USE_COUNT;
}
static void whiteheat_shutdown (struct usb_serial *serial)
{
struct usb_serial_port *command_port;
+ int i;
dbg(__FUNCTION__);
+ /* stop reads and writes on all ports */
+ for (i=0; i < serial->num_ports; ++i) {
+ while (serial->port[i].open_count > 0) {
+ whiteheat_close (&serial->port[i], NULL);
+ }
+ }
+
/* free up our private data for our command port */
command_port = &serial->port[COMMAND_PORT];
if (command_port->private != NULL) {
{
usb_serial_register (&whiteheat_fake_device);
usb_serial_register (&whiteheat_device);
+ info(DRIVER_VERSION " " DRIVER_DESC);
return 0;
}
module_init(whiteheat_init);
module_exit(whiteheat_exit);
-MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
-MODULE_DESCRIPTION("USB ConnectTech WhiteHEAT driver");
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+
+MODULE_PARM(debug, "i");
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
* drivers/usb/usb.c
*
* (C) Copyright Linus Torvalds 1999
- * (C) Copyright Johannes Erdfelt 1999
+ * (C) Copyright Johannes Erdfelt 1999-2001
* (C) Copyright Andreas Gal 1999
* (C) Copyright Gregory P. Smith 1999
* (C) Copyright Deti Fliegl 1999 (new USB architecture)
* Think of this as a "USB library" rather than anything else.
* It should be considered a slave, with no callbacks. Callbacks
* are evil.
- *
- * $Id: usb.c,v 1.53 2000/01/14 16:19:09 acher Exp $
*/
#include <linux/config.h>
#include <linux/bitops.h>
#include <linux/malloc.h>
#include <linux/interrupt.h> /* for in_interrupt() */
+#include <linux/init.h>
#if defined(CONFIG_KMOD) && defined(CONFIG_HOTPLUG)
0;
#endif
+extern int usb_hub_init(void);
+extern void usb_hub_cleanup(void);
+
/*
* Prototypes for the device driver probing/loading functions
*/
static struct usb_driver *usb_minors[16];
+/**
+ * usb_register - register a USB driver
+ * @new_driver: USB operations for the driver
+ *
+ * Registers a USB driver with the USB core. The list of unattached
+ * interfaces will be rescanned whenever a new driver is added, allowing
+ * the new driver to attach to any recognized devices.
+ * Returns a negative error code on failure and 0 on success.
+ */
int usb_register(struct usb_driver *new_driver)
{
if (new_driver->fops != NULL) {
init_MUTEX(&new_driver->serialize);
/* Add it to the list of known drivers */
- list_add(&new_driver->driver_list, &usb_driver_list);
+ list_add_tail(&new_driver->driver_list, &usb_driver_list);
usb_scan_devices();
return 0;
}
-/*
- * We go through all existing devices, and see if any of them would
- * be acceptable to the new driver.. This is done using a depth-first
- * search for devices without a registered driver already, then
- * running 'probe' with each of the drivers registered on every one
- * of these.
+/**
+ * usb_scan_devices - scans all unclaimed USB interfaces
+ *
+ * Goes through all unclaimed USB interfaces, and offers them to all
+ * registered USB drivers through the 'probe' function.
+ * This will automatically be called after usb_register is called.
+ * It is called by some of the USB subsystems after one of their subdrivers
+ * are registered.
*/
void usb_scan_devices(void)
{
down(&driver->serialize);
driver->disconnect(dev, interface->private_data);
up(&driver->serialize);
- usb_driver_release_interface(driver, interface);
+ /* if driver->disconnect didn't release the interface */
+ if (interface->driver)
+ usb_driver_release_interface(driver, interface);
/*
* This will go through the list looking for another
* driver that can handle the device
}
}
-/*
- * Unlink a driver from the driver list when it is unloaded
+/**
+ * usb_deregister - unregister a USB driver
+ * @driver: USB operations of the driver to unregister
+ *
+ * Unlinks the specified driver from the internal USB driver list.
*/
void usb_deregister(struct usb_driver *driver)
{
urb->bandwidth = 0;
}
-/*
- * New functions for (de)registering a controller
+static void usb_bus_get(struct usb_bus *bus)
+{
+ atomic_inc(&bus->refcnt);
+}
+
+static void usb_bus_put(struct usb_bus *bus)
+{
+ if (atomic_dec_and_test(&bus->refcnt))
+ kfree(bus);
+}
+
+/**
+ * usb_alloc_bus - creates a new USB host controller structure
+ * @op: pointer to a struct usb_operations that this bus structure should use
+ *
+ * Creates a USB host controller bus structure with the specified
+ * usb_operations and initializes all the necessary internal objects.
+ * (For use only by USB Host Controller Drivers.)
+ *
+ * If no memory is available, NULL is returned.
+ *
+ * The caller should call usb_free_bus() when it is finished with the structure.
*/
struct usb_bus *usb_alloc_bus(struct usb_operations *op)
{
memset(&bus->devmap, 0, sizeof(struct usb_devmap));
+#ifdef DEVNUM_ROUND_ROBIN
+ bus->devnum_next = 1;
+#endif /* DEVNUM_ROUND_ROBIN */
+
bus->op = op;
bus->root_hub = NULL;
bus->hcpriv = NULL;
INIT_LIST_HEAD(&bus->bus_list);
INIT_LIST_HEAD(&bus->inodes);
+ atomic_set(&bus->refcnt, 1);
+
return bus;
}
+/**
+ * usb_free_bus - frees the memory used by a bus structure
+ * @bus: pointer to the bus to free
+ *
+ * (For use only by USB Host Controller Drivers.)
+ */
void usb_free_bus(struct usb_bus *bus)
{
if (!bus)
return;
- kfree(bus);
+ usb_bus_put(bus);
}
+/**
+ * usb_register_bus - registers the USB host controller with the usb core
+ * @bus: pointer to the bus to register
+ *
+ * (For use only by USB Host Controller Drivers.)
+ */
void usb_register_bus(struct usb_bus *bus)
{
int busnum;
} else
warn("too many buses");
+ usb_bus_get(bus);
+
/* Add it to the list of buses */
list_add(&bus->bus_list, &usb_bus_list);
info("new USB bus registered, assigned bus number %d", bus->busnum);
}
+/**
+ * usb_deregister_bus - deregisters the USB host controller
+ * @bus: pointer to the bus to deregister
+ *
+ * (For use only by USB Host Controller Drivers.)
+ */
void usb_deregister_bus(struct usb_bus *bus)
{
info("USB bus %d deregistered", bus->busnum);
usbdevfs_remove_bus(bus);
clear_bit(bus->busnum, busmap.busmap);
+
+ usb_bus_put(bus);
}
/*
value = call_usermodehelper (argv [0], argv, envp);
kfree (buf);
kfree (envp);
- dbg ("kusbd policy returned 0x%x", value);
+ if (value != 0)
+ dbg ("kusbd policy returned 0x%x", value);
}
#else
memset(dev, 0, sizeof(*dev));
+ usb_bus_get(bus);
+
dev->bus = bus;
dev->parent = parent;
atomic_set(&dev->refcnt, 1);
if (atomic_dec_and_test(&dev->refcnt)) {
dev->bus->op->deallocate(dev);
usb_destroy_configuration(dev);
+
+ usb_bus_put(dev->bus);
+
kfree(dev);
}
}
{
atomic_inc(&dev->refcnt);
}
+
/* -------------------------------------------------------------------------------------
* New USB Core Functions
* -------------------------------------------------------------------------------------*/
+/**
+ * usb_alloc_urb - creates a new urb for a USB driver to use
+ * @iso_packets: number of iso packets for this urb
+ *
+ * Creates an urb for the USB driver to use and returns a pointer to it.
+ * If no memory is available, NULL is returned.
+ *
+ * If the driver want to use this urb for interrupt, control, or bulk
+ * endpoints, pass '0' as the number of iso packets.
+ *
+ * The driver should call usb_free_urb() when it is finished with the urb.
+ */
urb_t *usb_alloc_urb(int iso_packets)
{
urb_t *urb;
return urb;
}
-/*-------------------------------------------------------------------*/
+/**
+ * usb_free_urb - frees the memory used by a urb
+ * @urb: pointer to the urb to free
+ *
+ * If an urb is created with a call to usb_create_urb() it should be
+ * cleaned up with a call to usb_free_urb() when the driver is finished
+ * with it.
+ */
void usb_free_urb(urb_t* urb)
{
if (urb)
if (status) {
// something went wrong
usb_free_urb(urb);
+ current->state = TASK_RUNNING;
remove_wait_queue(&wqh, &wait);
return status;
}
} else
status = 1;
+ current->state = TASK_RUNNING;
remove_wait_queue(&wqh, &wait);
if (!status) {
}
-/*-------------------------------------------------------------------*/
+/**
+ * usb_control_msg - Builds a control urb, sends it off and waits for completion
+ * @dev: pointer to the usb device to send the message to
+ * @pipe: endpoint "pipe" to send the message to
+ * @request: USB message request value
+ * @requesttype: USB message request type value
+ * @value: USB message value
+ * @index: USB message index value
+ * @data: pointer to the data to send
+ * @size: length in bytes of the data to send
+ * @timeout: time to wait for the message to complete before timing out (if 0 the wait is forever)
+ *
+ * This function sends a simple control message to a specified endpoint
+ * and waits for the message to complete, or timeout.
+ *
+ * If successful, it returns 0, othwise a negative error number.
+ *
+ * Don't use this function from within an interrupt context, like a
+ * bottom half handler. If you need a asyncronous message, or need to send
+ * a message from within interrupt context, use usb_submit_urb()
+ */
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype,
__u16 value, __u16 index, void *data, __u16 size, int timeout)
{
return ret;
}
-/*-------------------------------------------------------------------*/
-/* compatibility wrapper, builds bulk urb, and waits for completion */
-/* synchronous behavior */
+/**
+ * usb_bulk_msg - Builds a bulk urb, sends it off and waits for completion
+ * @usb_dev: pointer to the usb device to send the message to
+ * @pipe: endpoint "pipe" to send the message to
+ * @data: pointer to the data to send
+ * @len: length in bytes of the data to send
+ * @actual_length: pointer to a location to put the actual length transfered in bytes
+ * @timeout: time to wait for the message to complete before timing out (if 0 the wait is forever)
+ *
+ * This function sends a simple bulk message to a specified endpoint
+ * and waits for the message to complete, or timeout.
+ *
+ * If successful, it returns 0, othwise a negative error number.
+ * The number of actual bytes transferred will be plaed in the
+ * actual_timeout paramater.
+ *
+ * Don't use this function from within an interrupt context, like a
+ * bottom half handler. If you need a asyncronous message, or need to
+ * send a message from within interrupt context, use usb_submit_urb()
+ */
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout)
{
info("USB disconnect on device %d", dev->devnum);
- call_policy ("remove", dev);
-
if (dev->actconfig) {
for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
struct usb_interface *interface = &dev->actconfig->interface[i];
down(&driver->serialize);
driver->disconnect(dev, interface->private_data);
up(&driver->serialize);
- usb_driver_release_interface(driver, interface);
+ /* if driver->disconnect didn't release the interface */
+ if (interface->driver)
+ usb_driver_release_interface(driver, interface);
}
}
}
usb_disconnect(child);
}
+ /* Let policy agent unload modules etc */
+ call_policy ("remove", dev);
+
/* Free the device number and remove the /proc/bus/usb entry */
if (dev->devnum > 0) {
clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
int devnum;
// FIXME needs locking for SMP!!
/* why? this is called only from the hub thread,
- * which hopefully doesn't run on multiple CPU's simulatenously 8-)
+ * which hopefully doesn't run on multiple CPU's simultaneously 8-)
*/
dev->descriptor.bMaxPacketSize0 = 8; /* Start off at 8 bytes */
+#ifndef DEVNUM_ROUND_ROBIN
devnum = find_next_zero_bit(dev->bus->devmap.devicemap, 128, 1);
+#else /* round_robin alloc of devnums */
+ /* Try to allocate the next devnum beginning at bus->devnum_next. */
+ devnum = find_next_zero_bit(dev->bus->devmap.devicemap, 128, dev->bus->devnum_next);
+ if (devnum >= 128)
+ devnum = find_next_zero_bit(dev->bus->devmap.devicemap, 128, 1);
+
+ dev->bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
+#endif /* round_robin alloc of devnums */
+
if (devnum < 128) {
set_bit(devnum, dev->bus->devmap.devicemap);
dev->devnum = devnum;
while (i--) {
if ((result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
- (type << 8) + index, 0, buf, size, HZ * GET_TIMEOUT)) >= 0 ||
+ (type << 8) + index, 0, buf, size, HZ * GET_TIMEOUT)) > 0 ||
result == -EPIPE)
- break;
+ break; /* retry if the returned length was 0; flaky device */
}
return result;
}
err = usb_set_address(dev);
if (err < 0) {
- err("USB device not accepting new address (error=%d)", err);
+ err("USB device not accepting new address=%d (error=%d)",
+ dev->devnum, err);
clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
dev->devnum = -1;
return 1;
if (err < 0)
err("unable to get device descriptor (error=%d)", err);
else
- err("USB device descriptor short read (expected %i, got %i)", sizeof(dev->descriptor), err);
+ err("USB device descriptor short read (expected %Zi, got %i)",
+ sizeof(dev->descriptor), err);
clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
dev->devnum = -1;
err = usb_get_configuration(dev);
if (err < 0) {
- err("unable to get configuration (error=%d)", err);
- usb_destroy_configuration(dev);
+ err("unable to get device %d configuration (error=%d)", dev->devnum, err);
clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
dev->devnum = -1;
+ usb_free_dev(dev);
return 1;
}
/* we set the default configuration here */
err = usb_set_configuration(dev, dev->config[0].bConfigurationValue);
if (err) {
- err("failed to set default configuration (error=%d)", err);
+ err("failed to set device %d default configuration (error=%d)",
+ dev->devnum, err);
clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
dev->devnum = -1;
return 1;
}
#endif
+
+/*
+ * Init
+ */
+static int __init usb_init(void)
+{
+ usb_major_init();
+ usbdevfs_init();
+ usb_hub_init();
+
+ return 0;
+}
+
+/*
+ * Cleanup
+ */
+static void __exit usb_exit(void)
+{
+ usb_major_cleanup();
+ usbdevfs_cleanup();
+ usb_hub_cleanup();
+}
+
+module_init(usb_init);
+module_exit(usb_exit);
+
/*
* USB may be built into the kernel or be built as modules.
* If the USB core [and maybe a host controller driver] is built
memset(&var, 0, sizeof(var));
#ifdef CONFIG_FB_OF
- /* New iBook */
- if (default_vmode == VMODE_CHOOSE &&
- machine_is_compatible("PowerBook2,2"))
- default_vmode = VMODE_800_600_60;
-
- if (default_vmode == VMODE_CHOOSE)
- var = default_var;
- else if (mac_vmode_to_var(default_vmode, default_cmode, &var))
- var = default_var;
+ if (_machine == _MACH_Pmac) {
+ if (mode_option) {
+ if (mac_vmode_to_var(default_vmode, default_cmode, &var))
+ var = default_var;
+ } else {
+ if (default_vmode <= 0 || default_vmode > VMODE_MAX)
+ default_vmode = VMODE_1024_768_60;
+
+ /* iBook SE */
+ if (machine_is_compatible("PowerBook2,2"))
+ default_vmode = VMODE_800_600_60;
+
+ /* iMacs and newer iBooks need to use 1024x768
+ * PowerMac2,1 first r128 iMacs
+ * PowerMac4,1 january 2001 iMacs "flower power"
+ */
+ if (machine_is_compatible("PowerMac2,1") ||
+ machine_is_compatible("PowerMac2,2") ||
+ machine_is_compatible("PowerMac4,1"))
+ default_vmode = VMODE_1024_768_75;
+
+ /* PowerBook Firewire (Pismo) and iBook2 */
+ if (machine_is_compatible("PowerBook3,1") ||
+ machine_is_compatible("PowerBook4,1"))
+ default_vmode = VMODE_1024_768_60;
+
+ /* PowerBook Titanium */
+ if (machine_is_compatible("PowerBook3,2"))
+ default_vmode = VMODE_1152_768_60;
+
+ if (default_cmode < CMODE_8 || default_cmode > CMODE_32)
+ default_cmode = CMODE_8;
+
+ if (mac_vmode_to_var(default_vmode, default_cmode, &var))
+ var = default_var;
+ }
+ } else
#else /* CONFIG_FB_OF */
var = default_var;
#endif /* CONFIG_FB_OF */
if (dp->name && !strcmp(dp->name, "ATY,RageM3pB"))
return;
+ if (dp->name && !strcmp(dp->name, "ATY,RageM3p12A") && dp->parent)
+ dp = dp->parent;
+ if (dp->name && !strcmp(dp->name, "ATY,RageM3p12B"))
+ return;
+
switch (dp->n_addrs) {
case 3:
fb_addr = dp->addrs[0].address;
FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
};
+ /* 1152x768, 60 Hz, Titanium PowerBook */
-static const struct mac_mode *mac_modes[20] = {
+static const struct mac_mode mac_mode_21 = {
+ VMODE_1152_768_60, 1152, 768,
+ 15386, 158, 26, 29, 3, 136, 6,
+ FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
+};
+
+ /* 1600x1024, 60 Hz, Non-Interlaced (112.27 MHz dotclock) */
+static const struct mac_mode mac_mode_22 = {
+ VMODE_1600_1024_60, 1600, 1024,
+ 8908, 88, 104, 1, 10, 16, 1,
+ FB_SYNC_HOR_HIGH_ACT|FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
+};
+
+static const struct mac_mode *mac_modes[22] = {
NULL, /* 512x384, 60Hz interlaced (NTSC) */
NULL, /* 512x384, 60Hz */
NULL, /* 640x480, 50Hz interlaced (PAL) */
&mac_mode_18, /* 1152x870, 75Hz */
&mac_mode_19, /* 1280x960, 75Hz */
&mac_mode_20, /* 1280x1024, 75Hz */
+ &mac_mode_21, /* 1152x768, 60Hz */
+ &mac_mode_22, /* 1600x1024, 60Hz */
};
static const struct mac_mode *mac_modes_inv[] = {
&mac_mode_18, /* 1152x870, 75Hz */
&mac_mode_19, /* 1280x960, 75Hz */
&mac_mode_20, /* 1280x1024, 75Hz */
+ &mac_mode_22, /* 1600x1024, 60Hz */
};
{ 0x730, VMODE_768_576_50I }, /* PAL (Alternate) */
{ 0x73a, VMODE_1152_870_75 }, /* 3rd party 19" */
{ 0x73f, VMODE_640_480_67 }, /* no sense lines connected at all */
+ { 0xBEEF, VMODE_1600_1024_60 }, /* 22" Apple Cinema Display */
{ -1, VMODE_640_480_60 }, /* catch-all, must be last */
};
#ifdef CONFIG_FB_ATY128
if (!strncmp(dp->name, "ATY,Rage128", 11) ||
!strncmp(dp->name, "ATY,RageM3p1", 12) ||
+ !strncmp(dp->name, "ATY,RageM3p2", 12) ||
!strncmp(dp->name, "ATY,RageM3pA", 12)) {
aty128fb_of_init(dp);
return 1;
" icm %0,2,1(%1)\n"
" icm %0,4,2(%1)\n"
" icm %0,8,3(%1)"
- : "=&a" (word) : "a" (p) );
+ : "=&a" (word) : "a" (p) : "cc" );
word >>= bit;
res = bit;
/* Look for zero in first longword */
"1: nr %1,0\n"
" ic %1,0(%1,%2)\n"
" alr %0,%1"
- : "+&d" (res), "+&d" (word)
+ : "+&d" (res), "+&a" (word)
: "a" (&_zb_findmap)
: "cc", "0" );
if (res < 32)
" icm %0,4,2(%1)\n"
" icm %0,2,1(%1)\n"
" ic %0,0(%1)"
- : "+&d" (x) : "a" (&temp) : "memory" );
+ : "+&d" (x) : "a" (&temp) : "memory", "cc" );
return x;
}
" icm %0,4,2(%1)\n"
" icm %0,2,1(%1)\n"
" ic %0,0(%1)"
- : "=&d" (result) : "a" (x) );
+ : "=&d" (result) : "a" (x) : "cc" );
return result;
}
" icm 0,2,1(%0)\n"
" ic 0,0(%0)\n"
" st 0,0(%0)"
- : : "a" (x) : "0", "memory");
+ : : "a" (x) : "0", "memory", "cc" );
}
static __inline__ __const__ __u16 ___arch__swab16(__u16 x)
" sth %0,0(%1)\n"
" icm %0,2,1(%1)\n"
" ic %0,0(%1)\n"
- : "+&d" (x) : "a" (&temp) : "memory");
+ : "+&d" (x) : "a" (&temp) : "memory", "cc" );
return x;
}
" sr %0,%0\n"
" icm %0,2,1(%1)\n"
" ic %0,0(%1)\n"
- : "=&d" (result) : "a" (x) );
+ : "=&d" (result) : "a" (x) : "cc" );
return result;
}
" icm 0,2,1(%0)\n"
" ic 0,0(%0)\n"
" sth 0,0(%0)"
- : : "a" (x) : "0", "memory");
+ : : "a" (x) : "0", "memory", "cc" );
}
#define __arch__swab32(x) ___arch__swab32(x)
struct task_struct *current;
__asm__("lhi %0,-8192\n\t"
"nr %0,15"
- : "=&r" (current) );
+ : "=&r" (current) : : "cc" );
return current;
}
#define DEBUG_DATA(entry) (char*)(entry + 1) /* data is stored behind */
/* the entry information */
-#define STCK(x) asm volatile ("STCK %0":"=m" (x))
+#define STCK(x) asm volatile ("STCK %0" : "=m" (x) : : "cc" )
typedef struct __debug_entry debug_entry_t;
" jz 0f\n"
" sr %0,%0\n"
"0:"
- : "=a" (real_address) : "a" (address) );
+ : "=a" (real_address) : "a" (address) : "cc" );
return real_address;
}
#define __LC_SUBCHANNEL_NR 0x0BA
#define __LC_IO_INT_PARM 0x0BC
#define __LC_MCCK_CODE 0x0E8
-#define __LC_AREGS_SAVE_AREA 0x200
-#define __LC_CREGS_SAVE_AREA 0x240
-#define __LC_RETURN_PSW 0x280
+#define __LC_AREGS_SAVE_AREA 0x120
+#define __LC_CREGS_SAVE_AREA 0x1c0
+#define __LC_RETURN_PSW 0x200
#define __LC_SYNC_IO_WORD 0x400
#define __LC_CPUID 0xC50
#define __LC_CPUADDR 0xC58
#define __LC_IPLDEV 0xC6C
+#define __LC_PANIC_MAGIC 0xE00
/* interrupt handler start with all io, external and mcck interrupt disabled */
__u32 failing_storage_address; /* 0x0f8 */
__u8 pad5[0x100-0xfc]; /* 0x0fc */
__u32 st_status_fixed_logout[4];/* 0x100 */
- __u8 pad6[0x160-0x110]; /* 0x110 */
+ __u8 pad6[0x120-0x110]; /* 0x110 */
+ __u32 access_regs_save_area[16];/* 0x120 */
__u32 floating_pt_save_area[8]; /* 0x160 */
__u32 gpregs_save_area[16]; /* 0x180 */
- __u8 pad7[0x200-0x1c0]; /* 0x1c0 */
+ __u32 cregs_save_area[16]; /* 0x1c0 */
- __u32 access_regs_save_area[16];/* 0x200 */
- __u32 cregs_save_area[16]; /* 0x240 */
- psw_t return_psw; /* 0x280 */
- __u8 pad8[0x400-0x288]; /* 0x288 */
+ psw_t return_psw; /* 0x200 */
+ __u8 pad8[0x400-0x208]; /* 0x208 */
__u32 sync_io_word; /* 0x400 */
atomic_t ext_call_fast; /* 0xc78 */
atomic_t ext_call_queue; /* 0xc7c */
atomic_t ext_call_count; /* 0xc80 */
+ __u8 pad10[0xe00-0xc84]; /* 0xc84 */
- /* Align SMP info to the top 1k of prefix area */
- __u8 pad10[0x1000-0xc84]; /* 0xc84 */
+ /* 0xe00 is used as indicator for dump tools */
+ /* whether the kernel died with panic() or not */
+ __u32 panic_magic; /* 0xe00 */
+
+ /* Align to the top 1k of prefix area */
+ __u8 pad11[0x1000-0xe04]; /* 0xe04 */
} __attribute__((packed)); /* End structure*/
extern __inline__ void set_prefix(__u32 address)
#endif
#endif /* __ASSEMBLY__ */
+#define __PANIC_MAGIC 0xDEADC0DE
+
#endif
"lr 4,%2\n\t"
"csp 2,4" :
: "d" (cs1), "d" (dum), "d" (adr)
- : "2", "3", "4");
+ : "2", "3", "4", "cc" );
}
static inline void __flush_global_tlb(void)
__asm__ __volatile(" ic 0,2(%0)\n"
" ipte %1,%2\n"
" stc 0,2(%0)"
- : : "a" (pte), "a" (pto), "a" (addr): "0");
+ : : "a" (pte), "a" (pto), "a" (addr): "0", "cc" );
}
/*
" stctl 0,15,0x1c0\n" /* store control registers */
" oi 0x1c0,0x10\n" /* fake protection bit */
" lpsw 0(%0)"
- : : "a" (dw_psw), "a" (&ctl_buf));
+ : : "a" (dw_psw), "a" (&ctl_buf) : "cc" );
}
#endif /* __ASM_S390_PROCESSOR_H */
" jl 1b\n"
" ex 0,4(2)" /* store *ptr to x */
: "+a&" (ptr) : "a" (&x)
- : "memory", "0", "1", "2");
+ : "memory", "cc", "0", "1", "2");
break;
case 2:
if(((__u32)ptr)&1)
" jl 1b\n"
" ex 0,4(2)" /* store *ptr to x */
: "+a&" (ptr) : "a" (&x)
- : "memory", "0", "1", "2");
+ : "memory", "cc", "0", "1", "2");
break;
case 4:
if(((__u32)ptr)&3)
" jl 0b\n"
" lr %0,0\n"
: "+d&" (x) : "a" (ptr)
- : "memory", "0" );
+ : "memory", "cc", "0" );
break;
default:
abort();
" st 0,0(1)\n" \
"1: ex %1,4(2)" /* execute lctl */ \
: "=m" (dummy) : "a" (cr*17), "a" (1<<(bit)) \
- : "0", "1", "2"); \
+ : "cc", "0", "1", "2"); \
})
#define __ctl_clear_bit(cr, bit) ({ \
" st 0,0(1)\n" \
"1: ex %1,4(2)" /* execute lctl */ \
: "=m" (dummy) : "a" (cr*17), "a" (~(1<<(bit))) \
- : "0", "1", "2"); \
+ : "cc", "0", "1", "2"); \
})
#ifdef __SMP__
".previous"
: "=m" (*((__u32*) ptr)) , "=&d" (err)
: "d" (x), "K" (-EFAULT)
- : "4" );
+ : "cc", "4" );
return err;
}
".previous"
: "=m" (*((__u16*) ptr)) , "=&d" (err)
: "d" (x), "K" (-EFAULT)
- : "4" );
+ : "cc", "4" );
return err;
}
".previous"
: "=m" (*((__u8*) ptr)) , "=&d" (err)
: "d" (x), "K" (-EFAULT)
- : "4" );
+ : "cc", "4" );
return err;
}
".previous" \
: "=d" (x) , "=&d" (err) \
: "m" (*(const __u32*) ptr), "K" (-EFAULT) \
- : "4" ); \
+ : "cc", "4" ); \
})
#define __get_user_asm_2(x, ptr, err) \
".previous" \
: "=d" (x) , "=&d" (err) \
: "m" (*(const __u16*) ptr), "K" (-EFAULT) \
- : "4" ); \
+ : "cc", "4" ); \
})
#define __get_user_asm_1(x, ptr, err) \
".previous" \
: "=d" (x) , "=&d" (err) \
: "m" (*(const __u8*) ptr), "K" (-EFAULT) \
- : "4" ); \
+ : "cc", "4" ); \
})
#define __get_user(x, ptr) \
" .long 0b,__copy_to_user_fixup\n"
".previous"
: "+&d" (n) : "d" (to), "d" (from)
- : "1", "2", "3", "4", "5" );
+ : "cc", "1", "2", "3", "4", "5" );
return n;
}
" .long 0b,__copy_from_user_fixup\n"
".previous"
: "+&d" (n) : "d" (to), "d" (from)
- : "1", "2", "3", "4", "5" );
+ : "cc", "1", "2", "3", "4", "5" );
return n;
}
: "=&a" (len)
: "a" (dst), "d" (src), "d" (count),
"K" (-EFAULT)
- : "2", "3", "4", "memory" );
+ : "2", "3", "4", "memory", "cc" );
return len;
}
: "cc", "0", "4" );
return n;
}
+#define strlen_user(str) strnlen_user(str, ~0UL)
/*
* Zero Userspace
extern int fddi_rebuild_header(struct sk_buff *skb);
extern unsigned short fddi_type_trans(struct sk_buff *skb,
struct device *dev);
+struct device *
+init_fddidev(struct device *dev, int sizeof_priv);
#endif
#endif /* _LINUX_FDDIDEVICE_H */
extern void unregister_netdev(struct device *dev);
extern int register_trdev(struct device *dev);
extern void unregister_trdev(struct device *dev);
+extern int register_fddidev(struct device *dev);
+extern void unregister_fddidev(struct device *dev);
extern int register_fcdev(struct device *dev);
extern void unregister_fcdev(struct device *dev);
/* Functions used for multicast support */
/* Endpoint descriptor */
struct usb_endpoint_descriptor {
- __u8 bLength;
- __u8 bDescriptorType;
- __u8 bEndpointAddress;
- __u8 bmAttributes;
- __u16 wMaxPacketSize;
- __u8 bInterval;
- __u8 bRefresh;
- __u8 bSynchAddress;
+ __u8 bLength __attribute__ ((packed));
+ __u8 bDescriptorType __attribute__ ((packed));
+ __u8 bEndpointAddress __attribute__ ((packed));
+ __u8 bmAttributes __attribute__ ((packed));
+ __u16 wMaxPacketSize __attribute__ ((packed));
+ __u8 bInterval __attribute__ ((packed));
+ __u8 bRefresh __attribute__ ((packed));
+ __u8 bSynchAddress __attribute__ ((packed));
unsigned char *extra; /* Extra descriptors */
int extralen;
-} __attribute__ ((packed));
+};
/* Interface descriptor */
struct usb_interface_descriptor {
- __u8 bLength;
- __u8 bDescriptorType;
- __u8 bInterfaceNumber;
- __u8 bAlternateSetting;
- __u8 bNumEndpoints;
- __u8 bInterfaceClass;
- __u8 bInterfaceSubClass;
- __u8 bInterfaceProtocol;
- __u8 iInterface;
+ __u8 bLength __attribute__ ((packed));
+ __u8 bDescriptorType __attribute__ ((packed));
+ __u8 bInterfaceNumber __attribute__ ((packed));
+ __u8 bAlternateSetting __attribute__ ((packed));
+ __u8 bNumEndpoints __attribute__ ((packed));
+ __u8 bInterfaceClass __attribute__ ((packed));
+ __u8 bInterfaceSubClass __attribute__ ((packed));
+ __u8 bInterfaceProtocol __attribute__ ((packed));
+ __u8 iInterface __attribute__ ((packed));
struct usb_endpoint_descriptor *endpoint;
- unsigned char *extra; /* Extra descriptors */
+ unsigned char *extra; /* Extra descriptors */
int extralen;
-} __attribute__ ((packed));
+};
struct usb_interface {
struct usb_interface_descriptor *altsetting;
/* Configuration descriptor information.. */
struct usb_config_descriptor {
- __u8 bLength;
- __u8 bDescriptorType;
- __u16 wTotalLength;
- __u8 bNumInterfaces;
- __u8 bConfigurationValue;
- __u8 iConfiguration;
- __u8 bmAttributes;
- __u8 MaxPower;
+ __u8 bLength __attribute__ ((packed));
+ __u8 bDescriptorType __attribute__ ((packed));
+ __u16 wTotalLength __attribute__ ((packed));
+ __u8 bNumInterfaces __attribute__ ((packed));
+ __u8 bConfigurationValue __attribute__ ((packed));
+ __u8 iConfiguration __attribute__ ((packed));
+ __u8 bmAttributes __attribute__ ((packed));
+ __u8 MaxPower __attribute__ ((packed));
struct usb_interface *interface;
- unsigned char *extra; /* Extra descriptors */
+ unsigned char *extra; /* Extra descriptors */
int extralen;
-} __attribute__ ((packed));
+};
/* String descriptor */
struct usb_string_descriptor {
struct semaphore serialize;
+ /* ioctl -- userspace apps can talk to drivers through usbdevfs */
int (*ioctl)(struct usb_device *dev, unsigned int code, void *buf);
};
#define USB_URB_EARLY_COMPLETE 0x0004
#define USB_ASYNC_UNLINK 0x0008
#define USB_QUEUE_BULK 0x0010
+#define USB_NO_FSBR 0x0020
+#define USB_ZERO_PACKET 0x0040 // Finish bulk OUTs always with zero length packet
#define USB_TIMEOUT_KILLED 0x1000 // only set by HCD!
typedef struct
struct usb_device *dev; // pointer to associated USB device
unsigned int pipe; // pipe information
int status; // returned status
- unsigned int transfer_flags; // USB_DISABLE_SPD | USB_ISO_ASAP | USB_URB_EARLY_COMPLETE
+ unsigned int transfer_flags; // USB_DISABLE_SPD | USB_ISO_ASAP | etc.
void *transfer_buffer; // associated data buffer
int transfer_buffer_length; // data buffer length
int actual_length; // actual data buffer length
int (*unlink_urb) (struct urb* purb);
};
+#define DEVNUM_ROUND_ROBIN /***** OPTION *****/
+
/*
* Allocated per bus we have
*/
struct usb_bus {
int busnum; /* Bus number (in order of reg) */
+#ifdef DEVNUM_ROUND_ROBIN
+ int devnum_next; /* Next open device number in round-robin allocation */
+#endif /* DEVNUM_ROUND_ROBIN */
+
struct usb_devmap devmap; /* Device map */
struct usb_operations *op; /* Operations (specific to the HC) */
struct usb_device *root_hub; /* Root hub */
/* usbdevfs inode list */
struct list_head inodes;
+
+ atomic_t refcnt;
};
#define USB_MAXCHILDREN (16) /* This is arbitrary */
#else /* CONFIG_USB_DEVICEFS */
-extern inline void usbdevfs_add_bus(struct usb_bus *bus) {}
-extern inline void usbdevfs_remove_bus(struct usb_bus *bus) {}
-extern inline void usbdevfs_add_device(struct usb_device *dev) {}
-extern inline void usbdevfs_remove_device(struct usb_device *dev) {}
+static inline void usbdevfs_add_bus(struct usb_bus *bus) {}
+static inline void usbdevfs_remove_bus(struct usb_bus *bus) {}
+static inline void usbdevfs_add_device(struct usb_device *dev) {}
+static inline void usbdevfs_remove_device(struct usb_device *dev) {}
-extern inline int usbdevfs_init(void) { return 0; }
-extern inline void usbdevfs_cleanup(void) { }
+static inline int usbdevfs_init(void) { return 0; }
+static inline void usbdevfs_cleanup(void) { }
#endif /* CONFIG_USB_DEVICEFS */
#define VMODE_1152_870_75 18 /* 1152x870, 75Hz */
#define VMODE_1280_960_75 19 /* 1280x960, 75Hz */
#define VMODE_1280_1024_75 20 /* 1280x1024, 75Hz */
-#define VMODE_MAX 20
+#define VMODE_1152_768_60 21 /* 1152x768, 60Hz */
+#define VMODE_1600_1024_60 22 /* 1600x1024, 60Hz */
+#define VMODE_MAX 22
#define VMODE_CHOOSE 99
#define CMODE_NVRAM -1
* <sshore@escape.ca>
* Scottie Shore : added support for mIRC DCC resume negotiation
* <sshore@escape.ca>
+ * Juan Jose Ciarlante : src addr/port checking for better security (spotted by Michal Zalewski)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* /etc/conf.modules (or /etc/modules.conf depending on your config)
* where modload will pick it up should you use modload to load your
* modules.
- *
+ *
+ * Insecure "back" data channel opening
+ * The helper does some trivial checks when opening a new DCC data
+ * channel. Use module parameter
+ * insecure=1
+ * ... to avoid this and get previous (pre 2.2.20) behaviour.
*/
#include <linux/config.h>
MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_MASQ_APP_PORTS) "i");
+static int insecure=0;
+MODULE_PARM(insecure, "i");
+
/*
* List of supported DCC protocols
return 0;
}
+
+/*
+ * Ugly workaround [TM] --mummy ... why does this protocol sucks?
+ *
+ * The <1024 check and same source address just raise the
+ * security "feeling" => they don't prevent a redirector listening
+ * in same src address at a higher port; you should protect
+ * your internal network with ipchains output rules anyway
+ */
+
+static inline int masq_irc_out_check(const struct ip_masq *ms, __u32 data_saddr, __u16 data_sport) {
+ int allow=1;
+
+ IP_MASQ_DEBUG(1-debug, "masq_irc_out_check( s_addr=%d.%d.%d.%d, data_saddr=%d.%d.%d.%d, data_sport=%d",
+ NIPQUAD(ms->saddr), NIPQUAD(data_saddr), ntohs(data_sport));
+
+ /*
+ * Ignore data channel back to other src addr, nor to port < 1024
+ */
+ if (ms->saddr != data_saddr || ntohs(data_sport) < 1024)
+ allow=0;
+
+ return allow;
+}
int
masq_irc_out (struct ip_masq_app *mapp, struct ip_masq *ms, struct sk_buff **skb_p, __u32 maddr)
{
struct tcphdr *th;
char *data, *data_limit;
__u32 s_addr;
- __u16 s_port;
+ __u32 s_port; /* larger to allow strtoul() return value validation */
struct ip_masq *n_ms;
char buf[20]; /* "m_addr m_port" (dec base)*/
unsigned buf_len;
s_port = simple_strtoul(data,&data,10);
addr_end_p = data;
+ /* Sanity checks */
+ if (!s_addr || !s_port || s_port > 65535)
+ continue;
+
+ /* Prefer net order from now on */
+ s_addr = htonl(s_addr);
+ s_port = htons(s_port);
+
+ /* Simple validation */
+ if (!insecure && !masq_irc_out_check(ms, s_addr, s_port))
+ /* We may just: return 0; */
+ continue;
+
/* Do we already have a port open for this client?
* If so, use it (for DCC ACCEPT)
*/
n_ms = ip_masq_out_get(IPPROTO_TCP,
- htonl(s_addr),htons(s_port),
+ s_addr, s_port,
0, 0);
/*
if (n_ms==NULL)
n_ms = ip_masq_new(IPPROTO_TCP,
maddr, 0,
- htonl(s_addr),htons(s_port),
+ s_addr, s_port,
0, 0,
IP_MASQ_F_NO_DPORT|IP_MASQ_F_NO_DADDR);
if (n_ms==NULL)
diff = buf_len - (addr_end_p-addr_beg_p);
*addr_beg_p = '\0';
- IP_MASQ_DEBUG(1-debug, "masq_irc_out(): '%s' %X:%X detected (diff=%d)\n", dcc_p, s_addr,s_port, diff);
+ IP_MASQ_DEBUG(1-debug, "masq_irc_out(): '%s' %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d detected (diff=%d)\n", dcc_p,
+ NIPQUAD(s_addr), htons(s_port),
+ NIPQUAD(n_ms->maddr), htons(n_ms->mport),
+ diff);
/*
* No shift.