3270
The 3270 is a family of terminals compatible and often used with the S/390 and z/Architecture mainframes. They are relatively simple to code for, and act as a "streaming" terminal (that is, it receives commands and applies them to an state) - so it virtually works the same as a VT100.
Usage
The 3270 operates like a normal terminal - it has PF0-PF15 keys which many programs use to perform operations (especially interactive programs). Receiving data is done through a normal I/O interruption generated by the 3270.
A local non-SNA console known as the SYSG is present on modern day mainframes, to use said console the traditional 3270 commands are mostly deprecated - and instead relies exclusively on the SCLP - this local non-SNA version can't use traditional CCW command chains or CSS addressing.
Sending commands
3270 supports various CCW commands - all of them can be used, most important ones are CMD_WRITE_CR, CMD_WRITE_NOCR and CMD_READ_BUFFER. See Command SubSystem for more information about CCW commands.
#define X3270_CMD_SELECT 0x0B
#define X3270_CMD_SELECT_WRITE 0x4B
#define X3270_CMD_WRITE_NOCR (CSS_CMD_WRITE) // Write without carriage return
#define X3270_CMD_WRITE_CR 0x09 // Write with carriage return
#define X3270_CMD_READ_BUFFER (CSS_CMD_READ) // Read the buffer
#define X3270_CMD_READ_MODIFIED 0x06 // Read modified
#define X3270_CMD_NOP (CSS_CMD_CONTROL) // No operation
#define X3270_CMD_WSF 0x11 // Write Structured Field
Composing packets
Composing packets consists of creating a buffer with the desired 3270 commands (not to be confused with CCW commands). And sending it via a READ CCW.
Traditional HLASM
* USE =A(BUFFER) SINCE WE'RE NOT DECLARING BUFFER AS
* AN EXTERNAL SYMBOL
BUFFER
$SF (SKIP)
* TELL THE 3270 WHERE OUR TEXT SHOULD BE PLACED AT
* MOST 3270 TERMINALS CAN USE UP TO 24 ROWS AND 80 COLUMNS
* OTHER MODELS MAY HAVE MORE
$SBA (24,5)
* THE HELLO WORLD MESSAGE
DC C'HELLO 3270 WORLD!'
* TELL THE 3270 WE'RE ENDING THIS
$SF (SKIP)
Then an HLASM routine would send the CCW command to take =A(BUFFER) and the 3270 will load said buffer and start interpreting it.
C
On C there are no HLASM macros to assist in the creation of a buffer, instead it has to be created manually.
enum x3270_order {
X3270_ORDER_PROGRAM_TAB = 0x05,
X3270_ORDER_SET_BUFFER_ADDR = 0x11,
X3270_ORDER_INSERT_CURSOR = 0x13,
X3270_ORDER_ERASE_UNPROTECTED = 0x12,
X3270_ORDER_START_FIELD = 0x1D,
X3270_ORDER_START_FIELD_EXTENDED = 0x29,
X3270_ORDER_MODIFY_FIELD = 0x2C,
X3270_ORDER_SET_ATTRIBUTE = 0x28
};
Addressing
The 3270 has a legacy addressing scheme, relying exclusively on EBCDIC characters to represent addresses below a 4096 characters barrier (12-bits of addresses). This is not present on the local non-SNA version.
const unsigned char ebcdic_map[] = {
0x40, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0x4A, 0x4B,
0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
0xD8, 0xD9, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0xE2, 0xE3,
0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0x7A, 0x7B,
0x7C, 0x7D, 0x7E, 0x7F
};
uint16_t x3270_address(uint16_t addr)
{
uint8_t tmp[2];
// 12-bit conversion does not need to be done since it's higher than the imposed 4K limit
if(addr >= 0xfff) {
tmp[0] = (addr >> 8) & 0x3F;
tmp[1] = addr & 0x3F;
} else {
// Take only in account low 6-bits */
tmp[0] = ebcdic_map[(addr >> 6) & 0x3F];
tmp[1] = ebcdic_map[addr & 0x3F];
}
return *((uint16_t *)&tmp[0]);
}
Example write
Writing to the 3270 can be, generally broken down into several steps:
- An initial byte known as the Write Control Character (WCC)
- The SBA command to set the address the text is going to be written at
- The text itself, encoded in EBCDIC
- Another final SF to terminate (SKIP)
Some steps are optional, but are provided for completeness.
// the schid depends upon the system
#define X3270_SCHID 0x00010001
void x3270_compose_buffer(uint8_t *buf, size_t n)
{
const char *msg = "Hello world";
size_t offset = 0, addr;
// starting wcc
buf[offset++] = 0x00;
// sba will now set the address to 0,0 (top-left of the screen)
buf[offset++] = X3270_ORDER_SET_BUFFER_ADDR;
addr = x3270_address(0);
buf[offset++] = (addr >> 8) & 0xff;
buf[offset++] = addr & 0xff;
// the data that is to be written
memcpy(&buf[offset], msg, strlen(msg));
offset += strlen(msg);
// optional terminating sf
buf[offset++] = X3270_ORDER_START_FIELD;
buf[offset++] = 0x00;
// send the buffer to the 3270 via a ccw...
kernel_css_write(X3270_SCHID, buf, n);
}