[an error occurred while processing this directive]

HP OpenVMS Systems Documentation

Content starts here

OpenVMS Alpha Guide to 64-Bit Addressing and VLM Features


Previous Contents Index

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.


Previous Next Contents Index