OpenVMS Alpha Guide to 64-Bit Addressing and VLM
Features
S2DGB$L_32CDBADDR (S2DGB$PQ_64CDBADDR)
This field should contain the 32-bit (or 64-bit) virtual address of the
SCSI command data block (CDB) to be sent to the target by this
IO$_DIAGNOSE operation.
Note that S2DGB$L_32CDBADDR is a pointer to a longword, while
S2DGB$PQ_64CDBADDR is a pointer to a quadword.
S2DGB$L_32CDBLEN (S2DGB$L_64CDBLEN)
This field should contain the number of bytes in the SCSI command data
block (CDB) to be sent to the target by this IO$_DIAGNOSE operation.
(Legal values: 2 to 248. However, some ports may restrict CDBs to
smaller lengths. Recommended values: 2 to 16.)
S2DGB$L_32DATADDR (S2DGB$PQ_64DATADDR)
This field should contain the 32-bit (or 64-bit) virtual address of the
DATAIN or DATAOUT buffer to be used with this SCSI operation. If the
CDB being sent to the target does not use a DATAIN or DATAOUT buffer,
then this field should be zero.
Note that S2DGB$L_32DATADDR is a pointer to a longword, while
S2DGB$PQ_64DATADDR is a pointer to a quadword.
S2DGB$L_32DATLEN (S2DGB$L_64DATLEN)
This field should contain the number of bytes in the DATAIN or DATAOUT
buffer associated with this operation. If the CDB being sent to the
target does not use a DATAIN or DATAOUT buffer, then this field should
be zero. (Legal values: 0 to UCB$L_MAXBCNT. Recommended values: 0 to
65,536. All ports are required to support at least 65,536 byte data
transfers.)
S2DGB$L_32PADCNT (S2DGB$L_64PADCNT)
This field should contain the number of padding DATAIN or DATAOUT bytes
required by this operation. (Legal values: 0 to the maximum number of
bytes in a disk block on this system minus one. Current legal values: 0
to 511.)
S2DGB$L_32PHSTMO (S2DGB$L_64PHSTMO)
This field should contain the number of seconds that the port driver
should wait for a phase transition to occur or for delivery of an
expected interrupt. If S2DGB$V_ TAGGED_REQ is 1 or this field contains
a 0 or 1, then the current phase transition timeout setting will not be
changed. (Legal values: 0 to 300 [5 minutes].)
S2DGB$L_32DSCTMO (S2DGB$L_64DSCTMO)
This field should contain the number of seconds that the port driver
should wait for a disconnected transaction to reconnect. If
S2DGB$V_TAGGED_REQ is 1 or this field contains a 0 or 1, then the
current disconnect timeout setting will not be changed. (Legal values:
0 to 65,535 [about 18 hours].)
S2DGB$L_32SENSEADDR (S2DGB$PQ_64SENSEADDR)
If S2DGB$V_AUTOSENSE is 1, then this field should contain the 32-bit
(or 64-bit) virtual address of the sense buffer to be used by this SCSI
operation. If S2DGB$V_AUTOSENSE is 0, this field will be ignored.
Note that S2DGB$L_32SENSEADDR is a pointer to a longword, while
S2DGB$PQ_64SENSEADDR is a pointer to a quadword.
S2DGB$L_32SENSELEN (S2DGB$L_64SENSELEN)
If S2DGB$V_AUTOSENSE is 1, then this field should contain the number of
bytes in the sense buffer associated with this operation. (Legal
values: 0 to 255. Note: a value of 0 instructs the class driver to
discard any sense data received. Recommended value: 18. Some ports may
restrict the number of sense bytes to 18.) If S2DGB$V_AUTOSENSE is 0,
this field will be ignored.
7.4.1 64-Bit S2DGB Example
The following example shows how to set up a 64-bit S2DGB:
#include <s2dgbdef.h> /* Define S2DGB */
#include <far_pointers.h> /* Define VOID_PQ */
S2DGB diag_desc;
/* Set up some default S2DGB descriptor values */
diag_desc.s2dgb$l_opcode = OP_XCDB64 /* Use 64-bits */
diag_desc.s2dgb$l_flags = (S2DGB$M_READ | /* Flags*/
S2DGB$M_TAGGED_REQ |
S2DGB$M_AUTOSENSE);
diag_desc.s2dgb$v_tag = S2DGB$K_SIMPLE; /* SIMPLE que tag */
diag_desc.s2dgb$pq_64cdbaddr = (VOID_PQ)(&cdb[0]);/* Command addr */
diag_desc.s2dgb$l_64cdblen = 6; /* Command length */
diag_desc.s2dgb$pq_64dataddr = (VOID_PQ)(&buf[0]);/* Data addr */
diag_desc.s2dgb$l_64datlen = 20; /* Data length */
diag_desc.s2dgb$l_64padcnt = 0; /* Pad length */
diag_desc.s2dgb$l_64phstmo = 20; /* Phase timeout */
diag_desc.s2dgb$l_64dsctmo = 10; /* Disc timeout */
diag_desc.s2dgb$pq_64senseaddr = (VOID_PQ)(&asn[0]);/* Autosense addr */
diag_desc.s2dgb$l_64senselen = 255; /* Sense length */
diag_desc.s2dgb$l_reserved_1 = 0; /* Reserved */
.
.
.
status = sys$qiow(0, target_chan, IO$_DIAGNOSE, &iosb, 0, 0,
&diag_desc, S2DGB$K_XCDB64_LENGTH, 0, 0, 0, 0);
|
If all arguments are valid, the class driver will invoke the necessary
port functions to send the CDB, transfer the data, and return, save or
discard sense data as defined by the input S2DGB. Upon completion, the
return IOSB will have the following format:
The DKDRIVER, GKDRIVER, and MKDRIVER class drivers, which implement
other QIO functions, might intermix other tagged requests with
IO$_DIAGNOSE requests. The order in which requests are sent generally
matches the order in which requests are presented to the driver. An
exception to this ordering occurs when the driver receives REQUEST
SENSE for which autosense data previously has been recovered and
stored. In this case, the IO$_DIAGNOSE will complete immediately and no
command will be sent to the target.
The DKDRIVER, GKDRIVER, and MKDRIVER class drivers permit only one
IO$_DIAGNOSE operation to be active (in the start I/O routine) at given
time, except as described in the next paragraph. However, applications
must single thread IO$_DIAGNOSE requests in order to properly detect
the presence of sense data and send the required REQUEST SENSE command.
This is consistent with the VAX IO$_DIAGNOSE behavior. For example, if
three reads are issued with no waiting and the first read gets a CHECK
CONDITION, the sense data will be discarded by the target when the
second read arrives.
The DKDRIVER, GKDRIVER, and MKDRIVER drivers permit more than one
IO$_DIAGNOSE operation to be active (in the start I/O routine) only
when all active operations have the S2DGB$V_AUTOSENSE flag equal to 1.
Upon encountering the first IO$_DIAGNOSE with S2DGB$V_AUTOSENSE equal
to 0, the class driver will apply the restrictions described in the
previous paragraph.
Chapter 8 OpenVMS Alpha 64-Bit API Guidelines
This chapter describes the guidelines used to develop 64-bit interfaces
to support OpenVMS Alpha 64-bit virtual addressing. Application
programmers who are developing their own 64-bit application programming
interfaces might find this information useful.
These recommendations are not hard and fast rules. Most are examples of
good programming practices.
For more information about C pointer pragmas, see the DEC C User's Guide for OpenVMS Systems.
8.1 Quadword/Longword Argument Pointer Guidelines
Because OpenVMS Alpha 64-bit adressing support allows application
programs to access data in 64-bit address spaces, pointers that are not
32-bit sign-extended values (64-bit pointers) will become more common
within applications. Existing 32-bit APIs will continue to be
supported, and the existence of 64-bit pointers creates some potential
pitfalls that programmers must be aware of.
For example, 64-bit addresses may be inadvertently passed to a routine
that can handle only a 32-bit address. Another dimension of this would
be a new API that includes 64-bit pointers embedded in data structures.
Such pointers might be restricted to point to 32-bit address spaces
initially, residing within the new data structure as a sign-extended
32-bit value.
Routines should guard against programming errors where 64-bit addresses
are being passed instead of 32-bit addresses. This type of checking is
called sign-extension checking, which means that the
address is checked to ensure that the upper 32 bits are all zeros or
all ones, matching the value of bit 31. This checking can be performed
at the routine interface that is imposing this restriction.
When defining a new routine interface, you should consider the ease of
programming a call to the routine from a 32-bit source module. You
should also consider calls written in all OpenVMS programming
languages, not just those languages initially supporting 64-bit
addressing. To avoid promoting awkward programming practices for the
32-bit caller of a new routine, you should accommodate 32-bit callers
as well as 64-bit callers.
Arguments passed by reference that are restricted to reside in a
32-bit address space (P0/P1/S0/S1) should have their reference
addresses sign-extension checked.
The OpenVMS Calling Standard requires that 32-bit values passed to a
routine be sign-extended to 64-bits before the routine is called.
Therefore, the called routine always receives 64-bit values. A 32-bit
routine cannot tell if its caller correctly called the routine with a
32-bit address, unless the reference to the argument is checked for
sign-extension.
This sign-extension checking would also apply to the reference to a
descriptor when data is being passed to a routine by descriptor.
The called routine should return the error status SS$_ARG_GTR_32_BITS
if the sign-extension check fails.
Alternately, if you want the called routine to accept the data being
passed in a 64-bit location without error and if the sign-extension
check fails, the data can be copied by the called routine to a 32-bit
address space. The 32-bit address space to which the routine copies the
data can be local routine storage (that is, the current stack). If the
data is copied to a 32-bit location other than local storage, memory
leaks and reentrancy issues must be considered.
When new routines are developed, pointers to code and all data pointers
passed to the new routines should be accommodated in 64-bit address
spaces where possible. This is desirable even if the data is a routine
or is typically considered static data, which the
programmer, compiler, or linker would not normally put in a 64-bit
address space. When code and static data is supported in 64-bit address
spaces, this routine should not need additional changes.
32-bit descriptor arguments should be validated to be 32-bit
descriptors.
Routines that accept descriptors should test the fields that allow you
to distinguish the 32-bit and 64-bit descriptor forms. If a 64-bit
descriptor is received, the routine should return an error.
Most existing 32-bit routines will return or signal the error status
SS$_ACCVIO when incorrectly presented with a 64-bit descriptor for the
following reasons:
- The 64-bit form of a descriptor contains an MBO (must be one) word
at offset 0, where the 32-bit descriptor LENGTH is located, and
- An MBMO (must be minus one) longword at offset 4, where the 32-bit
descriptor's POINTER is located, as shown in the following figure:
Routines that accept arguments passed by 64-bit descriptors should
accommodate 32-bit descriptors as well as 64-bit descriptors.
New routines should accommodate 32-bit and 64-bit descriptors within
the same routine. The same argument can point to either a 32-bit or
64-bit descriptor. The 64-bit descriptor MBO word at offset 0 should be
tested for one, and the 64-bit descriptor MBMO longword at offset 4
should be tested for a minus one to distinguish between a 64-bit and
32-bit descriptor.
Consider an existing 32-bit routine that is being converted to handle
64-bit as well as 32-bit descriptors. If the input descriptor is
determined to be a 64-bit descriptor, the data being pointed to by the
64-bit descriptor can first be copied to a 32-bit memory location, then
a 32-bit descriptor would be created in 32-bit memory. This new 32-bit
descriptor can then be passed to the existing 32-bit code, so that no
further modifications need to be made internally to the routine.
32-bit item list arguments should be validated to be 32-bit item
lists.
Two forms of item lists are defined: item_list_2 and item_list_3. The
item_list_2 form of an item list consists of two longwords with the
first longword containing a length and item code fields, while the
second longword typically contains a buffer address.
The item_list_3 form of an item list consists of three longwords with
the first longword containing a length and item code fields, while the
second and third longwords typically contain a buffer address and a
return length address field.
Since two forms of 32-bit item lists exist, two forms of a 64-bit item
list are defined. Dubbed item_list_64a and item_list_64b, these item
list forms parallel their 32-bit counterparts. Both forms of item list
contain the MBO and MBMO fields at offsets 0 and 4 respectively. They
also each contain a word-sized item code field, a quadword-sized length
field, and a quadword-sized buffer address field. The item_list_64b
form of an item list contains an additional quadword for the return
length address field. The returned length is 64-bits.
Routines that accept item lists should test the fields that allow you
to distinguish the 32-bit and 64-bit item list forms. If a 64-bit item
list is received, the routine should return an error.
Most existing 32-bit routines will return or signal the error status
SS$_ACCVIO when incorrectly presented with a 64-bit item list for the
following reasons:
- The 64-bit form of an item list contains a MBO (must be one) word
at offset 0, where the 32-bit item list LENGTH is located, and
- An MBMO (must be minus one) longword at offset 4, where the 32-bit
item list's BUFFER ADDRESS is located as shown in Figure 8-1 and
Figure 8-2.
Figure 8-1 item_list_64a
Figure 8-2 item_list_64b
Routines that accept arguments passed by 64-bit item list arguments
should accommodate 32-bit item lists as well as 64-bit item lists.
New routines should accommodate 32-bit and 64-bit item lists within the
same routine. The same argument can point to either a 32-bit or 64-bit
item list. The 64-bit item list MBO word at offset 0 should be tested
for one, and the 64-bit item list MBMO longword at offset 4 should be
tested for a minus one to distinguish between a 64-bit and 32-bit item
list.
Avoid passing pointers by reference.
If passing a pointer by reference is necessary, as with certain memory
management routines, the pointer should be defined to be 64-bit wide.
Mixing 32-bit and 64-bit pointers can cause programming errors when the
caller incorrectly passes a 32-bit wide pointer by reference when a
64-bit wide pointer is expected.
If the called routine reads a 64-bit wide pointer that was allocated
only one longword by the programmer, the wrong address could be used by
the routine.
If the called routine returns a 64-bit pointer, and therefore writes a
64-bit wide address into a longword allocated by the programmer, data
corruption can occur.
Existing routines that are passed pointers by reference require new
interfaces for 64-bit support. Old routine interfaces would still be
passed the pointer in a 32-bit wide memory location and the new routine
interface would require that the pointer be passed in a 64-bit wide
memory location. Keeping the same interface and passing it 64-bit wide
pointers would break existing programs.
Example: The return virtual address used in the
SYS$CRETVA_64 service is an example of when it is acceptable to pass a
pointer by reference. Virtual addresses created in P0 and P1 space are
guaranteed to have only 32 bits of significance, however all 64 bits
are returned. SYS$CRETVA_64 can also create address space in 64-bit
space and thus return a 64-bit address. The value that is returned must
always be 64 bits because a 64-bit address can be returned.
Memory allocation routines should return the pointer to the data
allocated by value (that is, in R0), if possible. The C allocation
routines, malloc, calloc, and realloc are examples of this.
New interfaces for routines that are not memory management routines
should avoid defining output arguments to receive addresses. Problems
will arise whenever a 64-bit subsystem allocates memory and then
returns a pointer back to a 32-bit caller in an output argument. The
caller may not be able to support or express a 64-bit pointer. Instead
of returning a pointer to some data, the caller should provide a
pointer to a buffer and the called routine should copy the data into
the user's buffer.
A 64-bit pointer passed by reference should be defined in such a way
that a call to the routine can be written in a 64-bit language or a
32-bit language. It should be clearly indicated that a 64-bit pointer
is required to be passed by all callers.
Routines must not return 64-bit addresses unless they are
specifically requested.
It is extremely important that routines which allocate memory and
return an address to their callers always allocate 32-bit addressable
memory, unless it is known absolutely that the caller is capable of
handling 64-bit addresses. This is true for both function return values
and output parameters. This rule prevents 64-bit addresses from
creeping in to applications which do not expect them. As a
result, programmers developing callable libraries should be
particularly careful to follow this rule.
Suppose an existing routine returns the address of memory it has
allocated, such as the routine value. If the routine accepts an input
parameter that in some way allows it to determine that the caller is
64-bit capable, it is safe to return a 64-bit address. Otherwise, it
must continue to return 32-bit, sign-extended address. In the
latter case, a new version of the routine could be provided, which
64-bit callers could invoke instead of the existing version if they
prefer that 64-bit memory be allocated.
Example: The routines in LIBRTL which manipulate
string descriptors can be sure that a caller is 64-bit capable if the
descriptor passed in is in the new 64-bit format. As a result, it is
safe for them to allocate 64-bit memory for string data, in that case.
Otherwise, they will continue to use only 32-bit addressable memory.
Avoid embedded pointers in data structures in public
interfaces.
If embedded pointers are necessary for a new structure in a new
interface, provide storage within the structure for a 64-bit pointer
(quadword aligned). The called routine, which may have to read the
pointer from the structure, simply reads all 64 bits.
If the pointer is constrained to be a 32-bit, sign-extended address
(for example, because the pointer will be passed to a 32-bit routine) a
sign-extension check should be performed on the 64-bit pointer at the
entrance to the routine. If the sign-extension check fails, the error
status SS$_ARG_GTR_32_BITS may be returned to the caller, or the data
found to reside in a 64-bit address space may be copied to a 32-bit
address space.
The new structure should be defined in such a way that a 64-bit caller
or a 32-bit caller does not contain awkward code. The structure should
provide a quadword field for the 64-bit caller overlaid with two
longword fields for the 32-bit caller. The first of these longwords is
the 32-bit pointer field and the next is an MBSE (must be
sign-extension) field. For most 32-bit callers, the MBSE field will be
zero because the pointer will be a 32-bit process space address. The
key here is to define the pointer as a 64-bit value and make it clear
to the 32-bit caller that the full quadword must be filled in.
In the following example, both 64-bit and 32-bit callers would pass a
pointer to the block structure and use the same
function prototype when calling the function routine. (Assume
data is an unknown structure defined in another module.)
#pragma required_pointer_size save
#pragma required_pointer_size 32
typedef struct block {
int blk_l_size;
int blk_l_flags;
union {
#pragma required_pointer_size 64
struct data *blk_pq_pointer;
#pragma required_pointer_size 32
struct {
struct data *blk_ps_pointer;
int blk_l_mbse;
} blk_r_long_struct;
} blk_r_pointer_union;
} BLOCK;
#define blk_pq_pointer blk_r_pointer_union.blk_pq_pointer
#define blk_r_long_struct blk_r_pointer_union.blk_r_long_struct
#define blk_ps_pointer blk_r_long_struct.blk_ps_pointer
#define blk_l_mbse blk_r_long_struct.blk_l_mbse
/* Routine accepts 64-bit pointer to the "block" structure */
#pragma required_pointer_size 64
int routine(struct block*);
#pragma required_pointer_size restore
|
For an existing 32-bit routine specifying an input argument, which is a
structure that embeds a pointer, you can use a different approach to
preserve the existing 32-bit interface. You can develop a 64-bit form
of the data structure that is distinguished from the 32-bit form of the
structure at run time. Existing code that accepts only the 32-bit form
of the structure should automatically fail when presented with the
64-bit form.
The structure definition for the new 64-bit structure should contain
the 32-bit form of the structure. Including the 32-bit form of the
structure allows the called routine to declare the input argument as a
pointer to the 64-bit form of the structure and cleanly handle both
cases.
Two different function prototypes can be provided for languages that
provide type checking. The default function prototype should specify
the argument as a pointer to the 32-bit form of the structure. The
64-bit form of the function prototype can be selected by defining a
symbol, specified by documentation.
The 64-bit versus 32-bit descriptor is an example of how this can be
done.
Example: In the following example, the state of the
symbol FOODEF64 selects the 64-bit form of the structure along
with the proper function prototype. If the symbol FOODEF64 is
undefined, the old 32-bit structure is defined and the old 32-bit
function prototype is used.
The source module that implements the function foo_print would
define the symbol FOODEF64 and be able to handle calls from
32-bit and 64-bit callers. The 64-bit caller would set the field
foo64$l_mbmo to -1. The routine foo_print would test
the field foo64$l_mbmo for -1 to determine if the caller used the
64-bit form of the structure or the 32-bit form of the structure.
#pragma required_pointer_size save
#pragma required_pointer_size 32
typedef struct foo {
short int foo$w_flags;
short int foo$w_type;
struct data * foo$ps_pointer;
} FOO;
#ifndef FOODEF64
/* Routine accepts 32-bit pointer to "foo" structure */
int foo_print(struct foo * foo_ptr);
#endif
#ifdef FOODEF64
typedef struct foo64 {
union {
struct {
short int foo64$w_flags;
short int foo64$w_type;
int foo64$l_mbmo;
#pragma required_pointer_size 64
struct data * foo64$pq_pointer;
#pragma required_pointer_size 32
} foo64$r_foo64_struct;
FOO foo64$r_foo32;
} foo64$r_foo_union;
} FOO64;
#define foo64$w_flags foo64$r_foo_union.foo64$r_foo64_struct.foo64$w_flags
#define foo64$w_type foo64$r_foo_union.foo64$r_foo64_struct.foo64$w_type
#define foo64$l_mbmo foo64$r_foo_union.foo64$r_foo64_struct.foo64$l_mbmo
#define foo64$pq_pointer foo64$r_foo_union.foo64$r_foo64_struct.foo64$pq_pointer
#define foo64$r_foo32 foo64$r_foo_union.foo64$r_foo32
/* Routine accepts 64-bit pointer to "foo64" structure */
#pragma required_pointer_size 64
int foo_print(struct foo64 * foo64_ptr);
#endif
#pragma required_pointer_size restore
|
In the previous example, if the structures foo and
foo64 will be used interchangeably within the same source
module, you can eliminate the symbol FOODEF64. The routine foo_print
would then be defined as follows:
int foo_print (void * foo_ptr);
|
Eliminating the FOODEF64 symbol allows 32-bit and 64-bit callers to use
the same function prototype, however less strict type checking is then
available during the C source compilation.
|