ibft.c revision 6cefaae1e90a413ba01560575bb3998e1a3df40e
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* This is the place to implement ld_ib_props()
* For x86 it is to load iBFT and costruct the global ib props
*/
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/mman.h>
#include <sys/bootprops.h>
#include <sys/kmem.h>
#include <sys/psm.h>
#ifndef NULL
#define NULL 0
#endif
typedef enum ibft_structure_type {
Reserved = 0,
Control = 1,
Initiator = 2,
Nic = 3,
Target = 4,
Extensions = 5,
Type_End
}ibft_struct_type;
typedef enum _chap_type {
NO_CHAP = 0,
CHAP = 1,
Mutual_CHAP = 2,
TYPE_UNKNOWN
}chap_type;
typedef struct ibft_entry {
int af;
int e_port;
char target_name[224];
char target_addr[INET6_ADDRSTRLEN];
}ibft_entry_t;
typedef struct iSCSI_ibft_tbl_hdr {
char Signature[4];
int Length;
char Revision;
char Checksum;
char oem_id[6];
char oem_table_id[8];
char Reserved[24];
}iscsi_ibft_tbl_hdr_t;
typedef struct iSCSI_ibft_hdr {
char Structure_id;
char Version;
ushort_t Length;
char Index;
char Flags;
}iscsi_ibft_hdr_t;
typedef struct iSCSI_ibft_control {
iscsi_ibft_hdr_t header;
ushort_t Extensions;
ushort_t Initiator_offset;
ushort_t Nic0_offset;
ushort_t Target0_offset;
ushort_t Nic1_offset;
ushort_t Target1_offset;
}iscsi_ibft_ctl_t;
typedef struct iSCSI_ibft_initiator {
iscsi_ibft_hdr_t header;
uchar_t iSNS_Server[16];
uchar_t SLP_Server[16];
uchar_t Pri_Radius_Server[16];
uchar_t Sec_Radius_Server[16];
ushort_t ini_name_len;
ushort_t ini_name_offset;
}iscsi_ibft_initiator_t;
typedef struct iSCSI_ibft_nic {
iscsi_ibft_hdr_t header;
uchar_t ip_addr[16];
char Subnet_Mask_Prefix;
char Origin;
uchar_t Gateway[16];
uchar_t Primary_dns[16];
uchar_t Secondary_dns[16];
uchar_t dhcp[16];
ushort_t vlan;
char mac[6];
ushort_t pci_BDF;
ushort_t Hostname_len;
ushort_t Hostname_offset;
}iscsi_ibft_nic_t;
typedef struct iSCSI_ibft_target {
iscsi_ibft_hdr_t header;
uchar_t ip_addr[16];
ushort_t port;
uchar_t boot_lun[8];
uchar_t chap_type;
uchar_t nic_association;
ushort_t target_name_len;
ushort_t target_name_offset;
ushort_t chap_name_len;
ushort_t chap_name_offset;
ushort_t chap_secret_len;
ushort_t chap_secret_offset;
ushort_t rev_chap_name_len;
ushort_t rev_chap_name_offset;
ushort_t rev_chap_secret_len;
ushort_t rev_chap_secret_offset;
}iscsi_ibft_tgt_t;
#define ISCSI_IBFT_LOWER_ADDR 0x80000 /* 512K */
#define ISCSI_IBFT_HIGHER_ADDR 0x100000 /* 1024K */
#define ISCSI_IBFT_SIGNATRUE "iBFT"
#define ISCSI_IBFT_SIGNATURE_LEN 4
#define ISCSI_IBFT_TBL_BUF_LEN 1024
#define ISCSI_IBFT_ALIGNED 16
#define ISCSI_IBFT_CTL_OFFSET 48
#define IBFT_BLOCK_VALID_YES 0x01 /* bit 0 */
#define IBFT_FIRMWARE_BOOT_SELECTED 0x02 /* bit 1 */
#define IBFT_USE_RADIUS_CHAP 0x04 /* bit 2 */
#define IBFT_USE_GLOBLE 0x04 /* NIC structure */
#define IBFT_USE_RADIUS_RHCAP 0x08 /* bit 3 */
/*
* Currently, we only support initiator offset, NIC0 offset, Target0 offset,
* NIC1 offset and Target1 offset. So the length is 5. If we want to support
* extensions, we should change this number.
*/
#define IBFT_OFFSET_BUF_LEN 5
#define IPV4_OFFSET 12
#define IBFT_INVALID_MSG "Invalid iBFT table 0x%x"
typedef enum ibft_status {
IBFT_STATUS_OK = 0,
/* General error */
IBFT_STATUS_ERR,
/* Bad header */
IBFT_STATUS_BADHDR,
/* Bad control ID */
IBFT_STATUS_BADCID,
/* Bad ip addr */
IBFT_STATUS_BADIP,
/* Bad af */
IBFT_STATUS_BADAF,
/* Bad chap name */
IBFT_STATUS_BADCHAPNAME,
/* Bad chap secret */
IBFT_STATUS_BADCHAPSEC,
/* Bad checksum */
IBFT_STATUS_BADCHECKSUM,
/* Low memory */
IBFT_STATUS_LOWMEM,
/* No table */
IBFT_STATUS_NOTABLE
} ibft_status_t;
extern void *memset(void *s, int c, size_t n);
extern int memcmp(const void *s1, const void *s2, size_t n);
extern void bcopy(const void *s1, void *s2, size_t n);
extern void iscsi_print_boot_property();
ib_boot_prop_t boot_property; /* static allocated */
extern ib_boot_prop_t *iscsiboot_prop; /* to be filled */
static ibft_status_t iscsi_parse_ibft_control(iscsi_ibft_ctl_t *ctl_hdr,
ushort_t *iscsi_offset_buf);
static ibft_status_t iscsi_parse_ibft_initiator(char *begin_of_ibft,
iscsi_ibft_initiator_t *initiator);
static ibft_status_t iscsi_parse_ibft_NIC(iscsi_ibft_nic_t *nicp);
static ibft_status_t iscsi_parse_ibft_target(char *begin_of_ibft,
iscsi_ibft_tgt_t *tgtp);
/*
* Return value:
* Success: IBFT_STATUS_OK
* Fail: IBFT_STATUS_BADCHECKSUM
*/
static ibft_status_t
iscsi_ibft_hdr_checksum(iscsi_ibft_tbl_hdr_t *tbl_hdr)
{
uchar_t checksum = 0;
uchar_t *start = NULL;
int length = 0;
int i = 0;
if (tbl_hdr == NULL) {
return (IBFT_STATUS_BADHDR);
}
length = tbl_hdr->Length;
start = (uchar_t *)tbl_hdr;
for (i = 0; i < length; i++) {
checksum = checksum + start[i];
}
if (!checksum)
return (IBFT_STATUS_OK);
else
return (IBFT_STATUS_BADCHECKSUM);
}
/*
* Now we only support one control structure in the IBFT.
* So there is no Control ID here.
*/
static ibft_status_t
iscsi_parse_ibft_structure(char *begin_of_ibft, char *buf)
{
iscsi_ibft_hdr_t *hdr = NULL;
ibft_status_t ret = IBFT_STATUS_OK;
if (buf == NULL) {
return (IBFT_STATUS_ERR);
}
hdr = (iscsi_ibft_hdr_t *)buf;
switch (hdr->Structure_id) {
case Initiator:
ret = iscsi_parse_ibft_initiator(
begin_of_ibft,
(iscsi_ibft_initiator_t *)buf);
break;
case Nic:
ret = iscsi_parse_ibft_NIC(
(iscsi_ibft_nic_t *)buf);
break;
case Target:
ret = iscsi_parse_ibft_target(
begin_of_ibft,
(iscsi_ibft_tgt_t *)buf);
break;
default:
ret = IBFT_STATUS_BADHDR;
break;
}
return (ret);
}
/*
* Parse the iBFT table
* return IBFT_STATUS_OK upon sucess
*/
static ibft_status_t
iscsi_parse_ibft_tbl(iscsi_ibft_tbl_hdr_t *tbl_hdr)
{
char *outbuf = NULL;
int i = 0;
ibft_status_t ret = IBFT_STATUS_OK;
ushort_t iscsi_offset_buf[IBFT_OFFSET_BUF_LEN] = {0};
if (tbl_hdr == NULL) {
return (IBFT_STATUS_ERR);
}
if (iscsi_ibft_hdr_checksum(tbl_hdr) != IBFT_STATUS_OK) {
return (IBFT_STATUS_BADCHECKSUM);
}
outbuf = (char *)tbl_hdr;
ret = iscsi_parse_ibft_control(
(iscsi_ibft_ctl_t *)&outbuf[ISCSI_IBFT_CTL_OFFSET],
iscsi_offset_buf);
if (ret == IBFT_STATUS_OK) {
ret = IBFT_STATUS_ERR;
for (i = 0; i < IBFT_OFFSET_BUF_LEN; i++) {
if (iscsi_offset_buf[i] != 0) {
ret = iscsi_parse_ibft_structure(
(char *)tbl_hdr,
(char *)tbl_hdr +
iscsi_offset_buf[i]);
if (ret != IBFT_STATUS_OK) {
return (ret);
}
}
}
}
return (ret);
}
static ibft_status_t
iscsi_parse_ibft_control(iscsi_ibft_ctl_t *ctl_hdr,
ushort_t *iscsi_offset_buf)
{
int i = 0;
ushort_t *offsetp = NULL;
if (ctl_hdr == NULL) {
return (IBFT_STATUS_BADHDR);
}
if (ctl_hdr->header.Structure_id != Control) {
return (IBFT_STATUS_BADCID);
}
/*
* Copy the offsets to offset buffer.
*/
for (offsetp = &(ctl_hdr->Initiator_offset); i < IBFT_OFFSET_BUF_LEN;
offsetp++) {
iscsi_offset_buf[i++] = *offsetp;
}
return (IBFT_STATUS_OK);
}
/*
* We only copy the "Firmare Boot Selseted" and valid initiator
* to the boot property.
*/
static ibft_status_t
iscsi_parse_ibft_initiator(char *begin_of_ibft,
iscsi_ibft_initiator_t *initiator)
{
if (initiator == NULL) {
return (IBFT_STATUS_ERR);
}
if (initiator->header.Structure_id != Initiator) {
return (IBFT_STATUS_BADHDR);
}
if ((initiator->header.Flags & IBFT_FIRMWARE_BOOT_SELECTED) &&
(initiator->header.Flags & IBFT_BLOCK_VALID_YES)) {
/*
* If the initiator name exists, we will copy it to our own
* property structure
*/
if (initiator->ini_name_len != 0) {
boot_property.boot_init.ini_name =
(uchar_t *)kmem_zalloc(
initiator->ini_name_len + 1, KM_SLEEP);
(void) snprintf(
(char *)boot_property.boot_init.ini_name,
initiator->ini_name_len + 1, "%s",
begin_of_ibft + initiator->ini_name_offset);
}
}
return (IBFT_STATUS_OK);
}
static ibft_status_t
iscsi_parse_ipaddr(uchar_t *source, char *dest, int *af)
{
int i = 0;
if (source == NULL) {
return (IBFT_STATUS_ERR);
}
if (source[0] == 0x00 && source[1] == 0x00 &&
source[2] == 0x00 && source[3] == 0x00 &&
source[4] == 0x00 && source[5] == 0x00 &&
source[6] == 0x00 && source[7] == 0x00 &&
source[8] == 0x00 && source[9] == 0x00 &&
(source[10] == 0xff) && (source[11] == 0xff)) {
/*
* IPv4 address
*/
if (dest != NULL) {
(void) sprintf(dest, "%d.%d.%d.%d",
source[12], source[13], source[14], source[15]);
}
if (af != NULL) {
*af = AF_INET;
}
} else {
if (dest != NULL) {
for (i = 0; i < 14; i = i + 2) {
(void) sprintf(dest, "%02x%02x:", source[i],
source[i+1]);
dest = dest + 5;
}
(void) sprintf(dest, "%02x%02x",
source[i], source[i+1]);
}
if (af != NULL) {
*af = AF_INET6;
}
}
return (IBFT_STATUS_OK);
}
/*
* Copy the ip address from ibft. If IPv4 is used, we should copy
* the address from 12th byte.
*/
static ibft_status_t
iscsi_copy_ibft_ipaddr(uchar_t *source, void *dest, int *af)
{
ibft_status_t ret = IBFT_STATUS_OK;
int sin_family = 0;
if (source == NULL || dest == NULL) {
return (IBFT_STATUS_ERR);
}
ret = iscsi_parse_ipaddr(source, NULL, &sin_family);
if (ret != 0) {
return (IBFT_STATUS_BADIP);
}
if (sin_family == AF_INET) {
bcopy(source+IPV4_OFFSET, dest, sizeof (struct in_addr));
} else if (sin_family == AF_INET6) {
bcopy(source, dest, sizeof (struct in6_addr));
} else {
return (IBFT_STATUS_BADAF);
}
if (af != NULL) {
*af = sin_family;
}
return (IBFT_STATUS_OK);
}
/*
* Maybe there are multiply NICs are available. We only copy the
* "Firmare Boot Selseted" and valid one to the boot property.
*/
static ibft_status_t
iscsi_parse_ibft_NIC(iscsi_ibft_nic_t *nicp)
{
ibft_status_t ret = IBFT_STATUS_OK;
int af = 0;
if (nicp == NULL) {
return (IBFT_STATUS_ERR);
}
if (nicp->header.Structure_id != Nic) {
return (IBFT_STATUS_ERR);
}
if ((nicp->header.Flags & IBFT_FIRMWARE_BOOT_SELECTED) &&
(nicp->header.Flags & IBFT_BLOCK_VALID_YES)) {
ret = iscsi_copy_ibft_ipaddr(nicp->ip_addr,
&boot_property.boot_nic.nic_ip_u, &af);
if (ret != IBFT_STATUS_OK) {
return (ret);
}
boot_property.boot_nic.sin_family = af;
ret = iscsi_copy_ibft_ipaddr(nicp->Gateway,
&boot_property.boot_nic.nic_gw_u, NULL);
if (ret != IBFT_STATUS_OK) {
return (ret);
}
ret = iscsi_copy_ibft_ipaddr(nicp->dhcp,
&boot_property.boot_nic.nic_dhcp_u, NULL);
if (ret != IBFT_STATUS_OK) {
return (ret);
}
bcopy(nicp->mac, boot_property.boot_nic.nic_mac, 6);
boot_property.boot_nic.sub_mask_prefix =
nicp->Subnet_Mask_Prefix;
}
return (IBFT_STATUS_OK);
}
/*
* Maybe there are multiply targets are available. We only copy the
* "Firmare Boot Selseted" and valid one to the boot property.
*/
static ibft_status_t
iscsi_parse_ibft_target(char *begin_of_ibft, iscsi_ibft_tgt_t *tgtp)
{
char *tmp = NULL;
int af = 0;
ibft_status_t ret = IBFT_STATUS_OK;
if (tgtp == NULL) {
return (IBFT_STATUS_ERR);
}
if (tgtp->header.Structure_id != Target) {
return (IBFT_STATUS_BADHDR);
}
if ((tgtp->header.Flags & IBFT_FIRMWARE_BOOT_SELECTED) &&
(tgtp->header.Flags & IBFT_BLOCK_VALID_YES)) {
/*
* Get Target Address
*/
ret = iscsi_copy_ibft_ipaddr(tgtp->ip_addr,
&boot_property.boot_tgt.tgt_ip_u, &af);
if (ret != IBFT_STATUS_OK) {
return (ret);
}
boot_property.boot_tgt.sin_family = af;
/*
* Get Target Name
*/
if (tgtp->target_name_len != 0) {
boot_property.boot_tgt.tgt_name =
(uchar_t *)kmem_zalloc(tgtp->target_name_len + 1,
KM_SLEEP);
(void) snprintf(
(char *)boot_property.boot_tgt.tgt_name,
tgtp->target_name_len + 1, "%s",
begin_of_ibft + tgtp->target_name_offset);
} else {
boot_property.boot_tgt.tgt_name = NULL;
}
/* Get Dest Port */
boot_property.boot_tgt.tgt_port = tgtp->port;
boot_property.boot_tgt.lun_online = 0;
/*
* Get CHAP secret and name.
*/
if (tgtp->chap_type != NO_CHAP) {
if (tgtp->chap_name_len != 0) {
boot_property.boot_init.ini_chap_name =
(uchar_t *)kmem_zalloc(
tgtp->chap_name_len + 1,
KM_SLEEP);
tmp = (char *)
boot_property.boot_init.ini_chap_name;
(void) snprintf(
tmp,
tgtp->chap_name_len + 1, "%s",
begin_of_ibft + tgtp->chap_name_offset);
} else {
/*
* Just set NULL, initiator is able to deal
* with this
*/
boot_property.boot_init.ini_chap_name = NULL;
}
if (tgtp->chap_secret_len != 0) {
boot_property.boot_init.ini_chap_sec =
(uchar_t *)kmem_zalloc(
tgtp->chap_secret_len + 1,
KM_SLEEP);
bcopy(begin_of_ibft +
tgtp->chap_secret_offset,
boot_property.boot_init.ini_chap_sec,
tgtp->chap_secret_len);
} else {
boot_property.boot_init.ini_chap_sec = NULL;
return (IBFT_STATUS_ERR);
}
if (tgtp->chap_type == Mutual_CHAP) {
if (tgtp->rev_chap_name_len != 0) {
boot_property.boot_tgt.tgt_chap_name =
(uchar_t *)kmem_zalloc(
tgtp->chap_name_len + 1,
KM_SLEEP);
#define TGT_CHAP_NAME boot_property.boot_tgt.tgt_chap_name
tmp = (char *)TGT_CHAP_NAME;
#undef TGT_CHAP_NAME
(void) snprintf(
tmp,
tgtp->chap_name_len + 1,
"%s",
begin_of_ibft +
tgtp->rev_chap_name_offset);
} else {
/*
* Just set NULL, initiator is able
* to deal with this
*/
boot_property.boot_tgt.tgt_chap_name =
NULL;
}
if (tgtp->rev_chap_secret_len != 0) {
boot_property.boot_tgt.tgt_chap_sec =
(uchar_t *)kmem_zalloc(
tgtp->rev_chap_secret_len + 1,
KM_SLEEP);
tmp = (char *)
boot_property.boot_tgt.tgt_chap_sec;
(void) snprintf(
tmp,
tgtp->rev_chap_secret_len + 1,
"%s",
begin_of_ibft +
tgtp->chap_secret_offset);
} else {
boot_property.boot_tgt.tgt_chap_sec =
NULL;
return (IBFT_STATUS_BADCHAPSEC);
}
}
} else {
boot_property.boot_init.ini_chap_name = NULL;
boot_property.boot_init.ini_chap_sec = NULL;
}
/*
* Get Boot LUN
*/
(void) bcopy(tgtp->boot_lun,
boot_property.boot_tgt.tgt_boot_lun, 8);
}
return (IBFT_STATUS_OK);
}
/*
* This function is used for scanning iBFT from the physical memory.
* Return Value:
* IBFT_STATUS_OK
* IBFT_STATUS_ERR
*/
static ibft_status_t
iscsi_scan_ibft_tbl(char *ibft_tbl_buf)
{
int start;
void *va = NULL;
int *len = NULL;
ibft_status_t ret = IBFT_STATUS_NOTABLE;
for (start = ISCSI_IBFT_LOWER_ADDR; start < ISCSI_IBFT_HIGHER_ADDR;
start = start + ISCSI_IBFT_ALIGNED) {
va = (void *)psm_map((paddr_t)(start&0xffffffff),
ISCSI_IBFT_SIGNATURE_LEN,
PROT_READ);
if (va == NULL) {
continue;
}
if (memcmp(va, ISCSI_IBFT_SIGNATRUE,
ISCSI_IBFT_SIGNATURE_LEN) == 0) {
ret = IBFT_STATUS_ERR;
/* Acquire table length */
len = (int *)psm_map(
(paddr_t)((start+\
ISCSI_IBFT_SIGNATURE_LEN)&0xffffffff),
ISCSI_IBFT_SIGNATURE_LEN, PROT_READ);
if (len == NULL) {
psm_unmap((caddr_t)va,
ISCSI_IBFT_SIGNATURE_LEN);
continue;
}
if (ISCSI_IBFT_LOWER_ADDR + *len <
ISCSI_IBFT_HIGHER_ADDR - 1) {
psm_unmap(va,
ISCSI_IBFT_SIGNATURE_LEN);
va = psm_map((paddr_t)(start&0xffffffff),
*len,
PROT_READ);
if (va != NULL) {
/*
* Copy data to our own buffer
*/
bcopy(va, ibft_tbl_buf, *len);
ret = IBFT_STATUS_OK;
}
psm_unmap((caddr_t)va, *len);
psm_unmap((caddr_t)len,
ISCSI_IBFT_SIGNATURE_LEN);
break;
} else {
psm_unmap((caddr_t)va,
ISCSI_IBFT_SIGNATURE_LEN);
psm_unmap((caddr_t)len,
ISCSI_IBFT_SIGNATURE_LEN);
}
} else {
psm_unmap((caddr_t)va, ISCSI_IBFT_SIGNATURE_LEN);
}
}
return (ret);
}
/*
* Scan the ibft table and store the iSCSI boot properties
* If there is a valid table then set the iscsiboot_prop
* iBF should be off if the host is not intended
* to be booted from iSCSI disk
*/
void
ld_ib_prop()
{
ibft_status_t ret = IBFT_STATUS_OK;
char *ibft_tbl_buf;
ibft_tbl_buf = (char *)kmem_zalloc(ISCSI_IBFT_TBL_BUF_LEN,
KM_SLEEP);
if (!ibft_tbl_buf) {
/* Unlikely to happen */
cmn_err(CE_NOTE, IBFT_INVALID_MSG,
IBFT_STATUS_LOWMEM);
return;
}
(void) memset(&boot_property, 0, sizeof (boot_property));
if ((ret = iscsi_scan_ibft_tbl(ibft_tbl_buf)) ==
IBFT_STATUS_OK) {
ret = iscsi_parse_ibft_tbl(
(iscsi_ibft_tbl_hdr_t *)ibft_tbl_buf);
if (ret == IBFT_STATUS_OK) {
iscsiboot_prop = &boot_property;
iscsi_print_boot_property();
} else {
cmn_err(CE_NOTE, IBFT_INVALID_MSG, ret);
}
} else if (ret != IBFT_STATUS_NOTABLE) {
cmn_err(CE_NOTE, IBFT_INVALID_MSG, ret);
}
kmem_free(ibft_tbl_buf, ISCSI_IBFT_TBL_BUF_LEN);
}