Commit b52c9487 authored by Jean-Claude BAU's avatar Jean-Claude BAU

HAL implementation for Low Phase Drift Calibration

parents 9da6b498 b17856a4
[submodule "userspace/mini-rpc"]
path = userspace/mini-rpc
url = git://github.com/rubini/mini-rpc.git
[submodule "userspace/ppsi"]
path = userspace/ppsi
url = ../ppsi.git
......@@ -10,15 +10,19 @@
*/
#ifndef __WBGEN2_REGDEFS_ENDPOINT-MDIO_WB
#define __WBGEN2_REGDEFS_ENDPOINT-MDIO_WB
#ifndef __WBGEN2_REGDEFS_ENDPOINT_MDIO_WB
#define __WBGEN2_REGDEFS_ENDPOINT_MDIO_WB
#ifdef __KERNEL__
#include <linux/types.h>
#else
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <stdint.h>
#endif
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
......@@ -371,6 +375,33 @@
#define MDIO_WR_SPEC_BSLIDE_W(value) WBGEN2_GEN_WRITE(value, 4, 5)
#define MDIO_WR_SPEC_BSLIDE_R(reg) WBGEN2_GEN_READ(reg, 4, 5)
/* definitions for register: MDIO Extended Control Register */
/* definitions for field: Loopback - detailed in reg: MDIO Extended Control Register */
#define MDIO_ECTRL_LPBCK_VEC_MASK WBGEN2_GEN_MASK(0, 3)
#define MDIO_ECTRL_LPBCK_VEC_SHIFT 0
#define MDIO_ECTRL_LPBCK_VEC_W(value) WBGEN2_GEN_WRITE(value, 0, 3)
#define MDIO_ECTRL_LPBCK_VEC_R(reg) WBGEN2_GEN_READ(reg, 0, 3)
/* definitions for field: SFP TX Fault Status in reg: MDIO Extended Control Register */
#define MDIO_ECTRL_SFP_TX_FAULT WBGEN2_GEN_MASK(3, 1)
/* definitions for field: SFP LOS in reg: MDIO Extended Control Register */
#define MDIO_ECTRL_SFP_LOSS WBGEN2_GEN_MASK(4, 1)
/* definitions for field: SFP TX Disable in reg: MDIO Extended Control Register */
#define MDIO_ECTRL_SFP_TX_DISABLE WBGEN2_GEN_MASK(5, 1)
/* definitions for field: tx_prbs_sel in reg: MDIO Extended Control Register */
#define MDIO_ECTRL_TX_PRBS_SEL_MASK WBGEN2_GEN_MASK(8, 3)
#define MDIO_ECTRL_TX_PRBS_SEL_SHIFT 8
#define MDIO_ECTRL_TX_PRBS_SEL_W(value) WBGEN2_GEN_WRITE(value, 8, 3)
#define MDIO_ECTRL_TX_PRBS_SEL_R(reg) WBGEN2_GEN_READ(reg, 8, 3)
/* definitions for register: Low phase drift calibration status register */
/* definitions for register: Low phase drift calibration control register */
PACKED struct MDIO_WB {
/* [0x0]: REG MDIO Control Register */
uint32_t MCR;
......@@ -392,6 +423,12 @@ PACKED struct MDIO_WB {
uint32_t ESTATUS;
/* [0x40]: REG WhiteRabbit-specific Configuration Register */
uint32_t WR_SPEC;
/* [0x44]: REG MDIO Extended Control Register */
uint32_t ECTRL;
/* [0x48]: REG Low phase drift calibration status register */
uint32_t LPC_PHY_STAT;
/* [0x4c]: REG Low phase drift calibration control register */
uint32_t LPC_PHY_CTRL;
};
#endif
......@@ -36,6 +36,7 @@
peripheral {
name = "WR Endpoint 1000base-X TBI PCS register block";
description = "WR Endpoint 1000base-X TBI PCS register block";
hdl_entity = "ep_pcs_tbi_mdio_wb";
prefix = "MDIO";
......@@ -153,15 +154,15 @@ peripheral {
field {
name = "Loopback";
description = "1 = enable loopback mode \
0 = disable loopback mode \
With the TBI version, loopback bit is connected to PHY loopback enable pin. When set to 1, indicates to the external PHY to enter loopback mode";
description = "1 enable loopback mode \
0 = disable loopback mode \
With the TBI version, loopback bit is connected to PHY loopback enable pin. When set to 1, indicates to the external PHY to enter loopback mode";
prefix = "loopback";
align = 14;
align = 14;
type = BIT;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
};
field {
......@@ -720,5 +721,99 @@ peripheral {
clock = "rx_clk_i";
};
};
reg {
name = "MDIO Extended Control Register";
prefix = "ECTRL";
field {
name = "Loopback - detailed";
description = "Loopback vector for Xilinx PHYs: \
100 = far end loopback mode \
000 = normal mode \
See also Transceiver documentation (for example Xilinx UG476 Table 2-37 and Figure 2-23 \
LOOPBACK bit set to 1 in MCR register may override LPBCK_VEC depending on the PHY wrapper implementation";
prefix = "lpbck_vec";
align = 0;
size = 3;
value = 0;
type = SLV;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
};
field {
name = "SFP TX Fault Status";
description = "1 = Some kind of laser failure\
0 = SFP Laser okay";
prefix = "sfp_tx_fault";
type = BIT;
access_bus=READ_ONLY;
access_dev=WRITE_ONLY;
};
field {
name = "SFP LOS";
description = "1 = Loss of signal\
0 = SFP Receiver signal strength okay";
prefix = "sfp_loss";
type = BIT;
access_bus=READ_ONLY;
access_dev=WRITE_ONLY;
};
field {
name = "SFP TX Disable";
description = "Disables the SFP Transmitter \
1 = SFP TX Disabled\
0 = SFP TX Enabled";
prefix = "sfp_tx_disable";
type = BIT;
value = 0;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
};
field {
name = "tx_prbs_sel";
description = "PRBS selection \
000 = Normal mode\
0010 = PRBS-7";
prefix = "tx_prbs_sel";
align = 8;
size = 3;
value = 0;
type = SLV;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
};
};
reg {
name = "Low phase drift calibration status register";
description = "Low phase drift calibration status register - dependent on calibrated PHY";
prefix = "LPC_PHY_STAT";
field {
name = "Control word 0";
size = 16;
type = SLV;
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
};
};
reg {
name = "Low phase drift calibration control register";
description = "Low phase drift calibration control register - dependent on calibrated PHY";
prefix = "LPC_PHY_CTRL";
field {
name = "Control word 0";
size = 16;
type = SLV;
access_bus = READ_WRITE;
access_dev = READ_ONLY;
};
};
};
......@@ -13,12 +13,16 @@
#ifndef __WBGEN2_REGDEFS_ENDPOINT
#define __WBGEN2_REGDEFS_ENDPOINT
#ifdef __KERNEL__
#include <linux/types.h>
#else
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <stdint.h>
#endif
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
......@@ -52,6 +56,9 @@
/* definitions for field: Receive path enable in reg: Endpoint Control Register */
#define EP_ECR_RX_EN WBGEN2_GEN_MASK(7, 1)
/* definitions for field: Generate preamble shrinkage in reg: Endpoint Control Register */
#define EP_ECR_TXSHRIN_EN WBGEN2_GEN_MASK(8, 1)
/* definitions for field: Feature present: VLAN tagging in reg: Endpoint Control Register */
#define EP_ECR_FEAT_VLAN WBGEN2_GEN_MASK(24, 1)
......@@ -64,6 +71,9 @@
/* definitions for field: Feature present: DPI packet classifier in reg: Endpoint Control Register */
#define EP_ECR_FEAT_DPI WBGEN2_GEN_MASK(27, 1)
/* definitions for field: Feature present: low phase drift calibration in reg: Endpoint Control Register */
#define EP_ECR_FEAT_LPC WBGEN2_GEN_MASK(28, 1)
/* definitions for register: Timestamping Control Register */
/* definitions for field: Transmit timestamping enable in reg: Timestamping Control Register */
......@@ -84,7 +94,6 @@
/* definitions for field: RX timestamper calibration result flag in reg: Timestamping Control Register */
#define EP_TSCR_RX_CAL_RESULT WBGEN2_GEN_MASK(5, 1)
/* definitions for register: RX Deframer Control Register */
/* definitions for field: RX accept runts in reg: RX Deframer Control Register */
......@@ -189,12 +198,18 @@
/* definitions for register: Flow Control Register */
/* definitions for field: RX Pause enable in reg: Flow Control Register */
/* definitions for field: RX Pause 802.3 enable in reg: Flow Control Register */
#define EP_FCR_RXPAUSE WBGEN2_GEN_MASK(0, 1)
/* definitions for field: TX Pause enable in reg: Flow Control Register */
/* definitions for field: TX Pause 802.3 enable in reg: Flow Control Register */
#define EP_FCR_TXPAUSE WBGEN2_GEN_MASK(1, 1)
/* definitions for field: Rx Pause 802.1Q enable in reg: Flow Control Register */
#define EP_FCR_RXPAUSE_802_1Q WBGEN2_GEN_MASK(2, 1)
/* definitions for field: Tx Pause 802.1Q enable (not implemented) in reg: Flow Control Register */
#define EP_FCR_TXPAUSE_802_1Q WBGEN2_GEN_MASK(3, 1)
/* definitions for field: TX pause threshold in reg: Flow Control Register */
#define EP_FCR_TX_THR_MASK WBGEN2_GEN_MASK(8, 8)
#define EP_FCR_TX_THR_SHIFT 8
......@@ -276,9 +291,35 @@
/* definitions for field: DMTD Phase shift value ready in reg: DMTD Status register */
#define EP_DMSR_PS_RDY WBGEN2_GEN_MASK(24, 1)
/* definitions for RAM: Event counters memory */
#define EP_RMON_RAM_BYTES 0x00000080 /* size in bytes */
#define EP_RMON_RAM_WORDS 0x00000020 /* size in 32-bit words, 32-bit aligned */
/* definitions for register: PCK Injection CTRL */
/* definitions for field: Config: Interframe GAP in reg: PCK Injection CTRL */
#define EP_INJ_CTRL_PIC_CONF_IFG_MASK WBGEN2_GEN_MASK(0, 16)
#define EP_INJ_CTRL_PIC_CONF_IFG_SHIFT 0
#define EP_INJ_CTRL_PIC_CONF_IFG_W(value) WBGEN2_GEN_WRITE(value, 0, 16)
#define EP_INJ_CTRL_PIC_CONF_IFG_R(reg) WBGEN2_GEN_READ(reg, 0, 16)
/* definitions for field: Config: packet pattern sel id in reg: PCK Injection CTRL */
#define EP_INJ_CTRL_PIC_CONF_SEL_MASK WBGEN2_GEN_MASK(16, 3)
#define EP_INJ_CTRL_PIC_CONF_SEL_SHIFT 16
#define EP_INJ_CTRL_PIC_CONF_SEL_W(value) WBGEN2_GEN_WRITE(value, 16, 3)
#define EP_INJ_CTRL_PIC_CONF_SEL_R(reg) WBGEN2_GEN_READ(reg, 16, 3)
/* definitions for field: Config: valid in reg: PCK Injection CTRL */
#define EP_INJ_CTRL_PIC_CONF_VALID WBGEN2_GEN_MASK(19, 1)
/* definitions for field: Mode: packet generate mode in reg: PCK Injection CTRL */
#define EP_INJ_CTRL_PIC_MODE_ID_MASK WBGEN2_GEN_MASK(20, 3)
#define EP_INJ_CTRL_PIC_MODE_ID_SHIFT 20
#define EP_INJ_CTRL_PIC_MODE_ID_W(value) WBGEN2_GEN_WRITE(value, 20, 3)
#define EP_INJ_CTRL_PIC_MODE_ID_R(reg) WBGEN2_GEN_READ(reg, 20, 3)
/* definitions for field: Mode: valid in reg: PCK Injection CTRL */
#define EP_INJ_CTRL_PIC_MODE_VALID WBGEN2_GEN_MASK(23, 1)
/* definitions for field: Frame Generation Enabled in reg: PCK Injection CTRL */
#define EP_INJ_CTRL_PIC_ENA WBGEN2_GEN_MASK(24, 1)
PACKED struct EP_WB {
/* [0x0]: REG Endpoint Control Register */
......@@ -315,10 +356,8 @@ PACKED struct EP_WB {
uint32_t DMCR;
/* [0x40]: REG DMTD Status register */
uint32_t DMSR;
/* padding to: 32 words */
uint32_t __padding_0[15];
/* [0x80 - 0xff]: RAM Event counters memory, 32 32-bit words, 32-bit aligned, word-addressable */
uint32_t RMON_RAM [32];
/* [0x44]: REG PCK Injection CTRL */
uint32_t INJ_CTRL;
};
#endif
......@@ -86,6 +86,17 @@ peripheral {
type = BIT;
};
field {
name = "Generate preamble shrinkage";
prefix = "TXSHRIN_en";
description = "1: TX preamble shrinkage enabled\
0: TX preamble shrinkage disabled";
access_bus = READ_WRITE;
access_dev = READ_ONLY;
type = BIT;
};
field {
name = "Feature present: VLAN tagging";
description = "1: this implementation of WR Endpoint supports VLAN processing \
......@@ -130,6 +141,16 @@ peripheral {
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
};
field {
name = "Feature present: low phase drift calibration";
description = "1: this port can be calibrated for low phase uncertainty on link up\
0: this is a standard WR port with regular performance";
prefix = "FEAT_LPC";
type = BIT;
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
};
};
......@@ -179,6 +200,24 @@ peripheral {
access_dev = WRITE_ONLY;
clock = "tx_clk_i";
};
field {
name = "Start calibration of RX timestamper";
prefix = "RX_CAL_START";
description = "write 1: start calibration.\
write 0: no effect";
type = MONOSTABLE;
clock = "rx_clk_i";
};
field {
name = "RX timestamper calibration result flag";
prefix = "RX_CAL_RESULT";
type = BIT;
access_bus = READ_ONLY;
access_dev = WRITE_ONLY;
};
};
reg {
......@@ -258,7 +297,6 @@ peripheral {
11: unqualified port - passes all traffic regardless of VLAN configuration";
type = SLV;
size = 2;
align = 2;
prefix = "Qmode";
access_dev = READ_ONLY;
access_bus = READ_WRITE;
......@@ -404,38 +442,50 @@ peripheral {
prefix = "FCR";
field {
name = "RX Pause enable";
description = "1: enable reception of pause frames and TX path throttling \
0: disable reception of pause frames";
name = "RX Pause 802.3 enable";
description = "1: enable reception of pause frames defined in 802.3 (all priorities) and TX path throttling \
0: disable reception of pause frames defined in 802.3";
prefix = "RXPAUSE";
access_bus = READ_WRITE;
access_dev = READ_WRITE;
access_dev = READ_ONLY;
type = BIT;
load = LOAD_EXT;
};
field {
name = "TX Pause enable";
name = "TX Pause 802.3 enable";
description = "1: enable transmission of pause frames and RX path throttling \
0: disable transmission of pause frames";
prefix = "TXPAUSE";
access_bus = READ_WRITE;
access_dev = READ_WRITE;
access_dev = READ_ONLY;
type = BIT;
};
field {
name = "Rx Pause 802.1Q enable";
description = "1: enable reception of priority-based pause frames (IEEE 802.1Q-2012 Flow Control) \
0: disable reception of priority-based pause frames";
prefix = "RXPAUSE_802_1Q";
access_bus = READ_WRITE;
access_dev = READ_ONLY;
type = BIT;
};
field {
name = "Tx Pause 802.1Q enable (not implemented)";
description = "1: enable transmission of priority-based pause frames (IEEE 802.1Q-2012) Flow Control \
0: disable transmission of priority-based pause frames";
prefix = "TXPAUSE_802_1Q";
access_bus = READ_WRITE;
access_dev = READ_ONLY;
type = BIT;
load = LOAD_EXT;
};
field {
name = "TX pause threshold";
description = "Defines the percentage of space occupied in the RX buffer which triggers the transmission of a PAUSE frame. 0 = empty buffer, 255 = full buffer";
prefix = "TX_THR";
access_bus = READ_WRITE;
access_dev = READ_WRITE;
access_dev = READ_ONLY;
type = SLV;
size = 8;
align = 8;
load = LOAD_EXT;
};
field {
......@@ -443,11 +493,10 @@ peripheral {
description = "Defines the quanta value carried bypause frames sent by the Endpoint";
prefix = "TX_QUANTA";
access_bus = READ_WRITE;
access_dev = READ_WRITE;
access_dev = READ_ONLY;
type = SLV;
size = 16;
align = 16;
load = LOAD_EXT;
};
};
......@@ -598,7 +647,7 @@ peripheral {
prefix = "EN";
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
load = LOAD_EXT;
};
field {
......@@ -610,7 +659,7 @@ peripheral {
align = 16;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
load = LOAD_EXT;
};
};
......@@ -636,30 +685,67 @@ peripheral {
access_bus = READ_WRITE;
};
};
reg {
name = "PCK Injection CTRL";
prefix = "INJ_CTRL";
field {
name = "Config: Interframe GAP";
prefix = "PIC_CONF_IFG";
size = 16;
type = SLV;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
};
ram {
name = "Event counters memory";
description = "RMON event counters:\
0x0 : TX PCS buffer underruns\
0x4 : RX PCS invalid 8b10b codes\
0x8 : RX PCS sync lost events\
0xc : RX PCS buffer overruns\
0x10: RX CRC errors\
0x14: RX valid frames\
0x18: RX runt frames\
0x1c: RX giant frames\
0x20: RX PCS errors\
0x24: RX dropped frames";
size = 32;
width = 32;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
field {
name = "Config: packet pattern sel id";
prefix = "PIC_CONF_SEL";
size = 3;
type = SLV;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
};
field {
name = "Config: valid";
prefix = "PIC_CONF_VALID";
type = BIT;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
};
field {
name = "Mode: packet generate mode";
prefix = "PIC_MODE_ID";
size = 3;
type = SLV;
align= 4;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
};
prefix = "rmon_ram";
};
field {
name = "Mode: valid";
prefix = "PIC_MODE_VALID";
type = BIT;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
};
field {
name = "Frame Generation Enabled";
prefix = "PIC_ENA";
type = BIT;
align = 4;
access_dev = READ_WRITE;
access_bus = READ_WRITE;
load = LOAD_EXT;
};
};
};
......@@ -117,7 +117,11 @@ int rts_lock_channel(int channel, int priority);
/* Enabled/disables phase tracking on a particular port */
int rts_enable_ptracker(int channel, int enable);
/* Enabled/disables phase tracking on a particular port */
/* Sets number of raw phase samples the DMTD uses to average to obtain the final phase value.
Lower number = faster measurements (useful during calibration). */
int rts_ptracker_set_average_samples(int channel, int avg_samples);
/* Debug stuff. Reserved for developers. Deliberately left undocumented. */
int rts_debug_command(int param, int value);
#ifdef RTIPC_EXPORT_STRUCTURES
......@@ -179,6 +183,17 @@ static struct minipc_pd rtipc_rts_debug_command_struct = {
},
};
static struct minipc_pd rtipc_rts_set_average_samples_struct = {
.name = "gggg",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int ),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int ),
MINIPC_ARG_END
},
};
#endif
#endif
-include ../Makefile.specific
# We are now Kconfig-based
-include ../../.config
......
-include ../Makefile.specific
# We are now Kconfig-based
-include ../../.config
......@@ -6,9 +7,10 @@ OBJS = init.o fpga_io.o util.o pps_gen.o i2c.o shw_io.o i2c_bitbang.o \
i2c_fpga_reg.o pio.o libshw_i2c.o i2c_sfp.o fan.o i2c_io.o hwiu.o \
ptpd_netif.o hal_client.o hal_minirpc.o\
shmem.o rt_client.o \
dot-config.o wrs-msg.o \
dot-config.o config-lpcalib.o wrs-msg.o \
mac.o \
timeout.o
timeout.o \
config-lpcalib.o generic_fsm.o
LIB = libwr.a
......
/*
* White Rabbit parser for Low Phase Drift Calibration configuration
* Copyright (C) 2018, CERN.
*
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* Description: re-written parser for configuration file. It allows
* to simultaneously read/write few files.
*
* NOTE: consider merging this with dot-config.c
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libwr/config-lpcalib.h>
struct key_value
{
char *comment;
char *key;
char *value;
struct key_value *next;
};
struct config_file
{
struct key_value *head;
};
static struct key_value *cfg_find_key(struct config_file *cfg, const char *key, int create)
{
struct key_value *kv = cfg->head, *kv_prev = cfg->head;
while (kv)
{
if (kv->key && !strcasecmp(key, kv->key))
{
return kv;
}
kv_prev = kv;
kv = kv->next;
}
if (!create)
return NULL;
if (!cfg->head)
{
cfg->head = malloc(sizeof(struct key_value));
kv = cfg->head;
}
else
{
kv_prev->next = malloc(sizeof(struct key_value));
kv = kv_prev->next;
}
kv->key = strdup(key);
kv->value = NULL;
kv->comment = NULL;
kv->next = NULL;
return kv;
}
int cfg_get_int(struct config_file *cfg, const char *key, int *value)
{
struct key_value *kv = cfg_find_key(cfg, key, 0);
if (!kv)
return 0;
*value = atoi(kv->value);
return 1;
}
struct config_file *cfg_load(const char *filename, int overwrite)
{
FILE *f = fopen(filename, "rb");
if (!f)
{
if (overwrite)
{
struct config_file *cfg = malloc(sizeof(struct config_file));
f = fopen(filename, "wb");
cfg->head = NULL;
return cfg;
}
return NULL;
}
struct config_file *cfg = malloc(sizeof(struct config_file));
cfg->head = NULL;
while (!feof(f))
{
char str[1024];
int rv = (int)fgets(str, 1024, f);
int i, pos = -1;
if (rv == 0)
break;
for (i = 0; i < strlen(str); i++)
{
if (str[i] == '=' && pos < 0)
pos = i;
if (str[i] == '\n' || str[i] == '\r')
str[i] = 0;
}
struct key_value *kv = malloc(sizeof(struct key_value));
if (pos < 0 || strlen(str) == 0 || str[0] == '#')
{
kv->comment = strdup(str);
kv->key = NULL;
kv->value = NULL;
}
else
{
kv->comment = NULL;
kv->key = strndup(str, pos);
kv->value = strdup(str + pos + 1);
kv->next = NULL;
}
if (!cfg->head)
{
cfg->head = kv;
}
else
{
struct key_value *last = cfg->head;
while (last->next)
last = last->next;
last->next = kv;
}
}
return cfg;
}
int cfg_set_int(struct config_file *cfg, const char *key, int value)
{
struct key_value *kv = cfg_find_key(cfg, key, 1);
if (!kv)
return 0;
char buf[1024];
snprintf(buf, 1024, "%d", value);
kv->value = strdup(buf);
return 1;
}
int cfg_save(struct config_file *cfg, const char *filename)
{
FILE *f = fopen(filename, "wb");
if (!f)
{
printf("Can't open '%s' for writing.\n", filename);
return -1;
}
struct key_value *kv = cfg->head;
while (kv)
{
if (kv->comment)
fprintf(f, "%s\n", kv->comment);
else
fprintf(f, "%s=%s\n", kv->key, kv->value);
kv = kv->next;
}
fclose(f);
return 0;
}
void cfg_close(struct config_file *cfg)
{
struct key_value *kv = cfg->head;
while (kv)
{
if (kv->comment)
free(kv->comment);
if (kv->key)
free(kv->key);
if (kv->value)
free(kv->value);
struct key_value *ptr = kv;
kv = kv->next;
free(ptr);
}
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*/
#include <stdlib.h>
#include <string.h>
#include <libwr/wrs-msg.h>
#include <libwr/generic_fsm.h>
static char * fsm_generic_build_events( fsm_t *pfg, int *eventMask );
/* Generic engine used by all ports states machine */
int fsm_generic_run( fsm_t *pfg) {
int ret=0;
int eventMask,isNewState;
fsm_generic_build_events(pfg, &eventMask);
pfg->lastEventMask = eventMask;
isNewState=fsm_has_pending_state(pfg);
/* Check if state has changed */
if ( isNewState ) {
fsm_consume_state(pfg); // update the current state with the scheduled one
}
/* Call state entry */
if (eventMask || isNewState) {
fsm_state_table_entry_t *pt;;
for( pt = pfg->state_table; pt->state >= 0; pt++)
{
if (pt->state == fsm_get_state(pfg) ) {
/* if ( FSM_GEN_DEBUG && pt->fctName!=NULL)
printf("%s: Calling %s (newState=%d, evts=%s),\n",
pfg->fsm_name,
pt->fctName, isNewState,evtStr);*/
ret= (*pt->handler)(pfg,eventMask,isNewState);
if ( fsm_has_pending_state(pfg) ) {
/* Consume state immediately */
return fsm_generic_run(pfg);
}
break;
}
}
}
return ret;
}
static char * fsm_generic_build_events( fsm_t *pfg, int *eventMask)
{
*eventMask=0;
if ( pfg->build_events_handler )
*eventMask=(*pfg->build_events_handler)(pfg);
if ( FSM_GEN_DEBUG ) {
static char str[128];
int copy=*eventMask;
fsm_event_table_entry_t *pe;
str[0]=0;
for( pe = pfg->event_table; pe->evtMask > 0; pe++)
{
if (copy==0) break;
if ( (pe->evtMask & copy) !=0 ) {
if ( str[0]!=0)
strcat(str,"+");
strcat(str,pe->evtName);
copy &=~pe->evtMask;
}
}
return str;
} else {
static char str[1]="";
return str;
}
}
/*
* Fill the FSM structure
* returns 0 on success or -1 on error
*/
int fsm_generic_create(fsm_t *fsm,
const char *name,
fsm_build_event_handler_t build_events_handler,
fsm_state_table_entry_t *state_table,
fsm_event_table_entry_t *event_table,
void *priv)
{
if (fsm->fsm_name!=NULL )
free(fsm->fsm_name); // In case the structure is filled more than one time
fsm->fsm_name = strdup(name);
if ( fsm->fsm_name==NULL )
return -1;
fsm->event_table = event_table;
fsm->state_table = state_table;
fsm->build_events_handler = build_events_handler;
fsm->enabled = 1;
fsm->priv = priv;
fsm_init_state(fsm);
return 0;
}
#ifndef __LIBWR_CONFIG2_H__
#define __LIBWR_CONFIG2_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct config_file;
extern int cfg_get_int(struct config_file *cfg, const char *key, int *value);
extern struct config_file *cfg_load(const char *filename, int overwrite);
extern int cfg_set_int(struct config_file *cfg, const char *key, int value);
extern int cfg_save(struct config_file *cfg, const char *filename);
extern void cfg_free(struct config_file *cfg);
extern void cfg_close(struct config_file *cfg);
#endif /* __LIBWR_CONFIG2_H__ */
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef GENERIC_FSM_H
#define GENERIC_FSM_H
#include <stdio.h>
#include <string.h>
#define FSM_GEN_DEBUG 0
#define FSM_SET_FCT_NAME(name) .handler=name, .fctName=#name
/*
* Used to define the callback to call for a givent state
*/
struct fsm_s;
/* States used by the generic FSM */
typedef struct {
int state;
int nextState;
} fsm_state_t;
typedef int (*fsm_state_handler_t)( struct fsm_s* fsm, int eventMsk, int newState);
typedef int (*fsm_build_event_handler_t)( struct fsm_s *fsm );
typedef struct {
int state;
char * stateName;
fsm_state_handler_t handler;
char *fctName;
} fsm_state_table_entry_t;
typedef struct {
int evtMask;
char * evtName;
} fsm_event_table_entry_t;
/*
* Structure containing all information needed by the state machine
*/
typedef struct fsm_s {
char * fsm_name;
fsm_build_event_handler_t build_events_handler;
fsm_state_table_entry_t *state_table;
fsm_event_table_entry_t *event_table;
fsm_state_t st;
int enabled;
int lastEventMask;
void *priv;
} fsm_t;
/* returns current state of the FSM */
static inline int fsm_get_state(fsm_t *fsm)
{
return fsm->st.state;
}
/* sets the current (immediate) state of the FSM */
static inline int fsm_set_state(fsm_t *fsm, int newState)
{
fsm->st.state = newState;
return fsm->st.state;
}
/* Set state and next state to -1 */
static inline int fsm_init_state(fsm_t *fsm)
{
fsm->st.state = fsm->st.nextState=-1;
return fsm->st.state;
}
/* gets the next (scheduled) state of the FSM */
static inline int fsm_get_next_state(fsm_t *fsm)
{
return fsm->st.nextState;
}
/* returns a human-readable state name for a given state */
static inline const char * fsm_get_state_name(fsm_t *fsm)
{
static const char *unknown_msg = "<Unknown>";
static const char *not_init_msg = "<NotInit>";
int st = fsm_get_state(fsm);
if (st == -1 )
return not_init_msg;
if (st < -1 )
return unknown_msg;
fsm_state_table_entry_t *pt = &fsm->state_table[ fsm_get_state(fsm) ];
return pt->stateName;
}
static inline const char *fsm_get_event_mask_as_string(fsm_t *fsm)
{
static char str[1024];
fsm_event_table_entry_t *evt;
int lastEventMask=fsm->lastEventMask;
str[0]=0;
for( evt = fsm->event_table; evt->evtMask > 0; evt++ )
{
if ( lastEventMask == 0 ) break;
if (lastEventMask & evt->evtMask)
{
if ( str[0]!=0 )
strcat(str," ");
strcat(str, evt->evtName);
lastEventMask &=~evt->evtMask;
}
}
if (lastEventMask!=0)
strcat(str," ???");
return str;
}
/* updates the current state of the FSM with the next (scheduled) state */
static inline void fsm_consume_state(fsm_t *fsm)
{
fsm->st.state = fsm_get_next_state(fsm);
if (FSM_GEN_DEBUG)
printf("%s: Enter state %s\n", fsm->fsm_name, fsm_get_state_name(fsm));
}
/* schedules next state of the FSM to newState */
static inline void fsm_fire_state(fsm_t *fsm, int newState)
{
fsm->st.nextState=newState;
}
/* returns nonzero if there's a next (scheduled) state present */
static inline int fsm_has_pending_state(fsm_t *fsm)
{
return fsm_get_state(fsm) != fsm_get_next_state(fsm);
}
/* prototypes */
/* Does a single turn of the FSM */
int fsm_generic_create(fsm_t *fsm, const char *name,
fsm_build_event_handler_t build_events_handler,
fsm_state_table_entry_t *state_table,
fsm_event_table_entry_t *event_table, void *priv);
int fsm_generic_run ( fsm_t *fsm );
#endif
#ifndef __LIBWR_HAL_SHMEM_H__
#define __LIBWR_HAL_SHMEM_H__
#include <string.h>
#include <hal_exports.h>
#include <libwr/sfp_lib.h>
#include <string.h>
#include <libwr/generic_fsm.h>
#include <libwr/timeout.h>
/* Port state machine states */
#define HAL_PORT_STATE_DISABLED 0
#define HAL_PORT_STATE_LINK_DOWN 1
#define HAL_PORT_STATE_UP 2
#define HAL_PORT_STATE_CALIBRATION 3
#define HAL_PORT_STATE_LOCKING 4
#define HAL_PORT_STATE_RESET 5
typedef enum {
HAL_PORT_STATE_INIT=0,
HAL_PORT_STATE_DISABLED,
HAL_PORT_STATE_LINK_DOWN,
HAL_PORT_STATE_LINK_UP,
} halPortState_t;
/* Read temperature from SFPs */
#define READ_SFP_DIAG_ENABLE 1
......@@ -61,31 +65,64 @@ typedef struct hal_port_calibration {
struct shw_sfp_dom sfp_dom_raw;
} hal_port_calibration_t;
/* Low Phase Drift Calibration for tx */
typedef struct {
int attempts;
int cal_saved_phase;
int cal_saved_phase_valid;
int measured_phase;
int expected_phase;
int tolerance;
int update_cnt;
int expected_phase_valid;
timeout_t calib_timeout;
}halPortLpdcTx_t;
/* Low Phase Drift Calibration for rx */
typedef struct {
timeout_t link_timeout;
timeout_t align_timeout;
timeout_t earlyup_timeout;
timeout_t align_to_link_timeout;
timeout_t restart_timeout;
int attempts;
}halPortLpdcRx_t;
typedef struct {
int numberOfLpdcPorts;
uint32_t maskUsedPorts;
uint32_t maskTxSetupDonePorts;
int firstLpdcPort;
int lastLpdcPort;
int calFileSynced;
}halGlobalLPDC_t; /* Global data for Low phase drift calibration */
typedef struct {
int isSupported; /* Set if Low Phase Drift Calibration is supported */
fsm_t txSetupFSM;
fsm_t rxSetupFSM;
timeout_t minCalibRx_timeout;
halPortLpdcTx_t *txSetup;
halPortLpdcRx_t *rxSetup;
halGlobalLPDC_t *globalLpdc;
}halPortLPDC_t; /* per-port data for Low phase drift calibration */
/* Internal port state structure */
struct hal_port_state {
/* non-zero: allocated */
int in_use;
/* linux i/f name */
char name[16];
/* MAC addr */
uint8_t hw_addr[6];
int in_use; /* non-zero: allocated */
char name[16]; /* linux i/f name */
uint8_t hw_addr[6]; /* MAC addr */
int hw_index; /* ioctl() hw index : 0..n */
/* ioctl() hw index */
int hw_index;
/* file descriptor for ioctls() */
int fd;
int fd; /* file descriptor for ioctls() */
int hw_addr_auto;
/* port FSM state (HAL_PORT_STATE_xxxx) */
int state;
/* fiber type, used to get alpha for SFP frequency */
int fiber_index;
fsm_t fsm;
fsm_t pllFsm;
/* 1: PLL is locked to this port */
int locked;
int fiber_index;/* fiber type, used to get alpha for SFP frequency */
int locked; /* 1: PLL is locked to this port */
/* calibration data */
hal_port_calibration_t calib;
......@@ -94,11 +131,10 @@ struct hal_port_state {
uint32_t phase_val;
int phase_val_valid;
int tx_cal_pending, rx_cal_pending;
/* locking FSM state */
int lock_state;
/*reference lock period in picoseconds*/
uint32_t clock_period;
int lock_state; /* locking FSM state */
uint32_t clock_period; /*reference lock period in picoseconds*/
/* approximate DMTD phase value (on slave port) at which RX timestamp
* (T2) counter transistion occurs (picoseconds) */
......@@ -108,11 +144,12 @@ struct hal_port_state {
* counter transistion occurs (picoseconds) */
uint32_t t4_phase_transition;
/* Endpoint's base address */
uint32_t ep_base;
uint32_t ep_base;/* Endpoint's base address */
/* whether SFP has diagnostic Monitoring capability */
int has_sfp_diag;
/* True if SFP is inserted */
int sfpPresent;
/* whether the port shall be monitored by SNMP */
int monitor;
......@@ -122,6 +159,14 @@ struct hal_port_state {
int synchronized; // <>0 if port is synchronized
int portInfoUpdated; // Set to 1 when updated
/* Events to process */
int evt_reset; /* Set if a reset is requested */
int evt_lock; /* Set if the ptracker must be activated*/
int evt_linkUp; /* Set if link is up ( driver call )*/
int evt_powerDown; /* Set if port is in power down state (MII MCR.PDOWN ==1 ) */
/* Low phase drift calibration data */
halPortLPDC_t lpdc;
};
struct hal_temp_sensors {
......@@ -132,21 +177,31 @@ struct hal_temp_sensors {
};
/* This is the overall structure stored in shared memory */
#define HAL_SHMEM_VERSION 12 /* Version 12, added monitor to
struct hal_port_state */
#define HAL_SHMEM_VERSION 15 /* Add share memory state*/
typedef enum {
HAL_SHMEM_STATE_NOT_INITITALIZED=0,
HAL_SHMEM_STATE_INITITALIZING,
HAL_SHMEM_STATE_INITITALIZED
}halShmemState_t;
struct hal_shmem_header {
int nports;
int hal_mode;
halShmemState_t shmemState;
struct hal_port_state *ports;
struct hal_temp_sensors temp;
int read_sfp_diag;
};
static inline int state_up(int state)
static inline int get_port_state(struct hal_port_state *ps)
{
return (state != HAL_PORT_STATE_LINK_DOWN
&& state != HAL_PORT_STATE_DISABLED);
return fsm_get_state( &ps->fsm );
}
static inline int state_up(struct hal_port_state *ps)
{
return get_port_state(ps) == HAL_PORT_STATE_LINK_UP;
}
static inline struct hal_port_state *hal_lookup_port(
......@@ -154,10 +209,14 @@ static inline struct hal_port_state *hal_lookup_port(
const char *name)
{
int i;
for (i = 0; i < nports; i++)
if (ports[i].in_use && (!strcmp(name, ports[i].name)))
return ports + i;
struct hal_port_state *p=ports;
for (i = 0; i < nports; i++) {
if (p->in_use && (!strcmp(name, p->name))) {
return p;
}
p++;
}
return NULL;
}
......
......@@ -2,10 +2,6 @@
#define __LIBWR_SHW_SFPLIB_H
/* note each led contains green and orange part */
#define SFP_LED_WRMODE_SLAVE (1) /* green */
#define SFP_LED_WRMODE_OTHER (2) /* orange */
#define SFP_LED_WRMODE_MASTER (3) /* yellow */
#define SFP_LED_WRMODE_OFF (4) /* to off entire WRMODE LED */
#define SFP_LED_WRMODE1 (1 << 0)
#define SFP_LED_WRMODE2 (1 << 1)
#define SFP_LED_SYNCED (1 << 2)
......
......@@ -238,7 +238,7 @@ struct wr_socket *ptpd_netif_create_socket(int sock_type, int flags,
hwconfig.tx_type = HWTSTAMP_TX_ON;
hwconfig.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
ifr.ifr_data = &hwconfig;
ifr.ifr_data = (char *)&hwconfig;
if (ioctl(fd, SIOCSHWTSTAMP, &ifr) < 0) {
perror("SIOCSHWTSTAMP");
......
......@@ -71,6 +71,19 @@ int rts_set_mode(int mode)
return rval;
}
int rts_ptracker_set_average_samples(int channel, int avg_samples)
{
int rval;
int ret = minipc_call(client, RTS_TIMEOUT,
&rtipc_rts_set_average_samples_struct, &rval, avg_samples);
if (ret < 0)
return ret;
return rval;
}
/* Sets the phase setpoint on a given channel */
int rts_adjust_phase(int channel, int32_t phase_setpoint)
{
......
Subproject commit 28dff05538f48b5eb68228e5a6db7712bf8b9795
*.o
*.a
*~
\ No newline at end of file
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-include ../Makefile.specific
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
CFLAGS = -Wall -ggdb -O2 -fno-strict-aliasing
LDFLAGS = -L. -lminipc -lm
# We need to support freestading environments: an embedded CPU that
# sits as a server on its own memory are and awaits commands
# The user can pass IPC_FREESTANDING=y
IPC_FREESTANDING ?= $(shell ./check-freestanding $(CC))
IPC_HOSTED ?= $(shell ./check-freestanding -n $(CC))
# Hosted is the opposite of freestanding, and cflags change too
ifeq ($(IPC_FREESTANDING),y)
IPC_HOSTED = n
CFLAGS += -ffreestanding -Iarch-$(ARCH)
else
IPC_HOSTED = y
endif
OBJ-$(IPC_HOSTED) = minipc-core.o minipc-server.o minipc-client.o
OBJ-$(IPC_FREESTANDING) = minipc-mem-server.o
LIB = libminipc.a
# export these to the examples (but if you make there IPC_HOSTED is default
export IPC_FREESTANDING IPC_HOSTED
all: $(LIB)
$(MAKE) -C examples
$(LIB): $(OBJ-y)
$(AR) r $@ $^
# the default puts LDFLAGS too early. Bah...
%: %.c
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
# This is stupid, it won't apply the first time, but, well... it works
$(wildcard *.o): $(wildcard *.h)
clean:
rm -f *.o *~ $(LIB)
$(MAKE) -C examples clean
install:
@echo "We have no install rule by now"
All documentation is now under the doc/ directory.
The input file is called doc/mini-ipc.in (it is readable by itself).
To have all usual output formats you can run "make" withing ./doc
but you need TexInfo installed.
#ifndef __ARCH_LM32_STDINT_H__
#define __ARCH_LM32_STDINT_H__
/*
* We miss a stdint.h in our compiler, so provide some types here,
* knowing the CPU is 32-bits and uses LP32 model
*/
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed long int32_t;
#endif /* __ARCH_LM32_STDINT_H__ */
#!/bin/sh
# I used to just check __STDC_HOSTED__ without special CFLAGS, but now I
# want to force freestanding compilation on otherwise hosted processors.
# Thus, the user can set IPC_FREESTANDING=y . If unset, this gets called.
# Accept "-n" to invert the result (select second field, not first)
if [ "x$1" = "x-n" ]; then select=2; shift; else select=1; fi
CC=$1
if [ "x$CC" = "x" ]; then
echo "$0: pass the compiler path (\$CC) as argument" >& 2
exit 1
fi
# Check the compiler: if it has no matches is prints the unadorned string
if [ "$($CC -print-file-name=libc.so)" = "libc.so" ]; then
res=y,n
else
res=n,y
fi
# If passed from the user, override the autodetected value
if [ "$IPC_FREESTANDING" = "y" ]; then
res=y,n
fi
echo $res | cut -d, -f $select
*~
*.aux
*.cp
*.fn
*.html
*.info
*.ky
*.log
*.pdf
*.pg
*.texi
*.toc
*.tp
*.txt
*.vr
#
# Makefile for the documentation directory
#
# Copyright 1994,2000,2010,2011 Alessandro Rubini <rubini@linux.it>
#
#################
# There is not basenames here, all *.in are considered input
INPUT = $(wildcard *.in)
TEXI = $(INPUT:.in=.texi)
INFO = $(INPUT:.in=.info)
HTML = $(INPUT:.in=.html)
TXT = $(INPUT:.in=.txt)
PDF = $(INPUT:.in=.pdf)
ALL = $(INFO) $(HTML) $(TXT) $(PDF)
MAKEINFO ?= makeinfo
%.texi: %.in
@rm -f $@
sed -f ./infofilter $< > $@
emacs -batch --no-site-file -l fixinfo $@
chmod -w $@
%.pdf: %.texi
texi2pdf --batch $<
%.info: %.texi
$(MAKEINFO) $< -o $@
%.html: %.texi
$(MAKEINFO) --html --no-split -o $@ $<
%.txt: %.texi
$(MAKEINFO) --no-headers $< > $@
##############################################
.PHONY: all images check terse clean install
.INTERMEDIATE: $(TEXI)
all: images $(ALL)
$(MAKE) terse
images::
if [ -d images ]; then $(MAKE) -C images || exit 1; fi
check: _err.ps
gs -sDEVICE=linux -r320x200x16 $<
terse:
for n in cp fn ky pg toc tp vr aux log; do rm -f *.$$n; done
rm -f *~
clean: terse
rm -f $(ALL) $(TEXI)
install:
;; use:
;; emacs -batch -l ./fixinfo.el <file>
;; or, better:
;; emacs -batch --no-site-file -l ./fixinfo.el <file>
(defun fixinfo (file)
(find-file-other-window file)
(message (concat "Maxing texinfo tree in " file))
(texinfo-all-menus-update)
(texinfo-every-node-update)
(save-buffer)
(kill-buffer (current-buffer))
)
;; loop over command line arguments
(mapcar 'fixinfo command-line-args-left)
(kill-emacs)
#! /usr/bin/sed -f
# allow "%" as a comment char, but only at the beginning of the line
s/^%/@c /
#s/[^\\]%.*$//
s/^\\%/%/
#preserve blanks and braces in @example blocks
/@example/,/@end example/ s/{/@{/g
/@example/,/@end example/ s/}/@}/g
/@example/,/@end example/ p
/@example/,/@end example/ d
/@smallexample/,/@end smallexample/ s/{/@{/g
/@smallexample/,/@end smallexample/ s/}/@}/g
/@smallexample/,/@end smallexample/ p
/@smallexample/,/@end smallexample/ d
# remove leading blanks
s/^[ ]*//
\input texinfo @c -*-texinfo-*-
%
% mini-ipc.in - main file for the documentation
%
%%%%
%------------------------------------------------------------------------------
%
% NOTE FOR THE UNAWARE USER
% =========================
%
% This file is a texinfo source. It isn't the binary file of some strange
% editor of mine. If you want ASCII, you should "make mini-ipc.txt".
%
%------------------------------------------------------------------------------
%
% This is not a conventional info file...
% I use three extra features:
% - The '%' as a comment marker, if at beginning of line ("\%" -> "%")
% - leading blanks are allowed (this is something I can't live without)
% - braces are automatically escaped when they appear in example blocks
%
@comment %**start of header
@documentlanguage en
@documentencoding ISO-8859-1
@setfilename mini-ipc.info
@settitle mini-ipc
@iftex
@afourpaper
@end iftex
@paragraphindent none
@comment %**end of header
@setchapternewpage off
@set update-month March 2012
@finalout
@titlepage
@title mini-ipc
@subtitle @value{update-month}
@subtitle A misnamed minimal RPC library
@author Alessandro Rubini (@code{rubini@@gnudd.com})
@end titlepage
@headings single
@c ##########################################################################
@iftex
@contents
@end iftex
@c ##########################################################################
@node Top
@top Introduction
This is a mini remote procedure call, but I called it IPC in error.
The short name throughout the project is @code{minipc}, used both as
a prefix for exported symbols and header/library name.
It is currently being suggested as a replacement for what we have in the
@i{White Rabbit} system, because the way it is there is not portable
(assembly code and knowledge of the ABI is required for each
platform). Nonetheless, I borrowed quite some ideas from the
implementation by Tomasz Wlostowski, who must definitely be
considered co-author and my inspiring muse.
This version also supports a @i{freestanding} mode, whereas a
memory-mapped embedded CPU can act as a server for a process
on the main CPU that acts as a client. The library offers the same
API as the normal hosted version, but only for server operation.
@menu
* Basic Ideas::
* The Main Data Structure::
* The Client::
* The Server::
* Diagnostics::
* The Communication Protocol::
* Transport Mechanisms::
* Freestanding Operation::
* Examples::
* Bugs::
@end menu
@c ##########################################################################
@node Basic Ideas
@chapter Basic Ideas
The package is meant to be as simple as possible, while still having
some abstraction to be useable on different environments than the
@i{White Rabbit Switch}. There are other RPC environments, but they are
usually oversized for our needs. Besides, I personally love small
things that I can use as completely-understandable examples when
teaching.
With this library, a process can be a server, or a client, or both.
A server program opens a Unix domain socket (or other communication channels)
and then accepts client programs. Thus,
performing a server action means either accepting a new client or
performing a client request. To avoid requiring the library
to be the only I/O of the process, I export the @i{fd_set} structure
that lists the file descriptors that are active in the server (I don't directly
support @code{poll(2)}, only @code{select(2)}).
A client program only works on a single file descriptor, so the library
provides a function to retrieve the fd (thus you can use poll yourself).
A function being exported is defined by a data structure, which lists both
the argument list and the return value. Data types supported are
integers, double-precision floating point numbers,
strings and generic structures. We might (or might
not) export the thing over TCP and add endian conversion, but at this
point the data type is really just informative.
An argument list is described by a zero-terminated array of 32-bit
integers, which specifies both the type and the size of the argument
being passed. The return value is described by a single 32-bit
integer specifying type and size. The @code{minipc.h} header provides
macros to build the actual values.
@c ##########################################################################
@node The Main Data Structure
@chapter The Main Data Structure
In order to communicate, client and server must agree on the functions
being called remotely. In the library, a function is identified by a
string name, 18 bytes at most.
Following is the "procedure definition" structure, from @code{minipc.h}.
Its length (on 32-bit machines) is 36 bytes if the function takes
no arguments, each argument is described by 4 more bytes.
@example
struct minipc_pd {
minipc_f *f; /* pointer to the function */
char name[20]; /* name of the function */
uint32_t flags;
uint32_t retval; /* type of return value */
uint32_t args[]; /* null-terminated list of arguments */
};
@end example
Client and server are expected to share such data structure, or at least
the macros used to build the data structure. The fields
@i{name}, @i{retval} and @i{args}, must match. The function pointer
is only used in the server. No
user-accessible flags are currently there.
The "args" array must be terminated by a "MINIPC_ARG_END" value, which
is guaranteed to be bitwise zero. The other arguments encode type and
size, even if some types (like integers, floats) always have the same
size. The header offers the following macros to encode and decode
the arguments (and retval). Please look at the examples to better
get the feeling of the code.
@table @code
@item MINIPC_ARG_ENCODE(atype, type)
The macro returns a 32-bit integer that includes the argument
type (see below for a list of @i{atypes}) and the size of @i{type}
(using @code{sizeof} internally). The size must be 64kB at most.
@item MINIPC_GET_ATYPE(word)
@itemx MINIPC_GET_ASIZE(word)
The functions return the type and size of an argument.
@item MINIPC_ARG_END
This is zero, the terminator for the argument list.
@end table
The supported types are the values of this enumeration (the error
special type is used internally by the library):
@example
enum minipc_at {
MINIPC_ATYPE_ERROR = 0xffff,
MINIPC_ATYPE_NONE = 0, /* used as terminator */
MINIPC_ATYPE_INT = 1,
MINIPC_ATYPE_INT64,
MINIPC_ATYPE_DOUBLE, /* float is promoted to double */
MINIPC_ATYPE_STRING, /* size is strlen() each time */
MINIPC_ATYPE_STRUCT
};
@end example
@c ##########################################################################
@node The Client
@chapter The Client
A process acting as a @i{minipc} client creates a client channel. The
@code{minipc_ch} structure includes a file descriptor that you can
retrieve. The following functions are thus available:
@example
struct minipc_ch *minipc_client_create(const char *name, int flags);
int minipc_fileno(struct minipc_ch *ch);
@end example
The name used to create a channel is a filename including no
path separators and no colon (@code{:}) character.
The library currently only supports Unix domain sockets, and the
filename is used to connect to a socket in the @code{MINIPC_BASE_PATH}
directory (default value: @code{/tmp/.minipc}). Other channels will
use colon-separated names, for example @code{TCP:@i{host}:@i{port}}
for TCP channels.
With the fileno a client may poll for reading, although this is not
usually needed because the server will only send reply packets:
exactly one reply packet for each request packet.
A client can make a request by calling this function:
@example
int minipc_call(struct minipc_ch *ch, int millisec_timeout,
const struct minipc_pd *pd, void *ret, ...);
@end example
The arguments are passed as a list of values (structures and strings
are passed by pointer); the return pointer is filled with the returned
data item (please look at the examples for actual use patterns).
The @code{minipc_call} function picks data from the variable arguments
according to the argument types set forth in the "pd" procedure
definition. Similarly, the return value is stored where pointed by
ret, according to pd->retval. Note that the return value must be at least
4 bytes long.
The @code{minipc_call} returns 0 on success and -1 on failure, setting
@code{errno} accordingly.. If @i{send}, @i{poll} or @i{recv} return
an error, the original @code{errno} is preserved. If @i{poll} times
out (using the provided timeout) @code{errno} is set to
@code{ETIMEDOUT}. If the server returns an error (using
@code{MINIPC_ATYPE_ERROR}), the local errno is set to @code{EREMOTEIO}
and the remote one is saved using the retval pointer (which is
guaranteer to point to an int-sized or bigger area).
To close the connection, a client can call
@example
int minipc_close(struct minipc_ch *ch);
@end example
@c ##########################################################################
@node The Server
@chapter The Server
The server must open a server channel and process requests as needed.
The library exports the following functions for server use:
@example
struct minipc_ch *minipc_server_create(const char *name, int flags);
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd);
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd);
int minipc_server_action(struct minipc_ch *ch, int timeoutms);
int minipc_server_get_fdset(struct minipc_ch *ch, fd_set *setptr);
@end example
The @i{export} and @i{unexport} functions should be self-explicative. Note
that the exported function name is part of the @code{pd} structure.
The @code{pd} passed to @i{minipc_unexport} must be the
same as the one passed to @i{minipc_export}, not just a pointer to a
data structure with the same contents.
The function @i{minipc_server_action} either accepts a new client or
handles all pending client requests. For every packet received from a
client, the function send back a reply packet. So, even if communication is
based on @code{SOCK_STREAM}, packet boundaries are preserved by using only
synchronous communication.
The @i{minipc_get_fdset} function returns an @i{fdset} structure, so the caller
may use select() in the main loop by augmenting the minipc @i{fdset}
with its own.
Internally, the server action calls @i{poll} by itself (with the
specified timeout). Thus, if you already used @i{select} you can pass
0 as @code{timeoutms} in minipc_server_action.
The header uses a @code{typedef} for exported functions, to ease their
definition:
@example
typedef int (minipc_f)(const struct minipc_pd *,
void *retval, void *args);
@end example
An exported function receives a pointer to its own description, a
pointer to the data being returned and a pointer to
the unmarshalled arguments (again, please look at the examples)
The function itself should pick up data from the pointers. Such
pointers are usually directed to the packet buffer, so you should not
change the arguments themselves before calling the real function with
the ABI of the current CPU.
For example, the code exporting @code{sqrt} looks like the following:
@example
extern double sqrt(double x);
int export_sqrt(const struct minipc_pd *pd, uint32_t *args, void *ret)
{
double in, out;
in = *(double *)args;
out = sqrt(in);
*(double *)retval = out;
return 0;
}
struct minipc_pd sqrt_pd = {
.f = export_sqrt,
.id.s = "sqrt",
.retval = MINIPC_ARG_ENCODE(MINIPC_AT_DOUBLE, double),
.args = {
MINIPC_ARG_ENCODE(MINIPC_AT_DOUBLE, double),
MINIPC_ARG_END,
},
}
@end example
Please note that while using doubles or integers is easy, unmarshalling
a string from the args array is not trivial. The library
offers a generic unmarshall function:
@example
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype);
@end example
The function receives the current pointer to an argument and the
current type. The function offers a portable solution to the
problem of strings or structure whose size is not a multiple of
4 bytes.
This is an example use of the function:
@example
int export_strcat(const struct minipc_pd *pd, uint32_t *args, void *ret)
{
char *s1, char *s2, char sout[256];
s1 = (char *)args;
args = minipc_get_next_arg(args, pd->args[0]);
s2 = (char *)args;
strncpy(sout, s1, sizeof(sout));
strncat(sout, s2, sizeof(sout) - strlen(sout));
strncpy(ret, sout, MINIPC_MAX_REPLY));
return 0;
}
@end example
The client that calls a server exporting @i{sqrt} (as shown above)
will do it in the following way. The code assumes
@code{struct minipc_ch *client} and @code{struct minipc_pd pd_sqrt}
are valid variables:
@example
double operand;
double result;
int error;
error = minipc_call(client, &pd_sqrt, &result, operand);
if (error < 0) { ... }
@end example
@c ##########################################################################
@node Diagnostics
@chapter Diagnostics
A @code{minipc_ch} channel may have an associated log file (you can
use stderr or whatever). This following function sets the log file:
@example
int minipc_set_logfile(struct minipc_ch *ch, FILE *logf);
@end example
You can set the logfile to NULL (default at channel creation) if you
want to stop sending diagnostics. The function does not open or close
the file, as otherwise it wouldn't be possible to use stderr, so if
you want to open a log file on disk, it's your duty to do so; in this
case you possibly want to make it unbuffered, since the library only
calls @i{fprintf} on it, with no explicit @i{fflush}.
@c ##########################################################################
@node The Communication Protocol
@chapter The Communication Protocol
The protocol itself is an internal detail, but worth knowing in case a
bug is discovered. Relevant structures are defined in the internal header
@code{minipc-int.h}.
Request packets are sent as @code{struct mpc_req_packet}, which
includes the following items:
@itemize @bullet
@item 20 bytes for the function name, copied from "pd" structure
@item an array of 32-bit integers for the arguments.
@end itemize
Each argument passed to the remote function is stored into one or
more such 32-bit integers, in host byte order. Note that the
actual size of the argument is written in the "pd" args list.
Marshalling is performed by minipc_call() with @i{varargs} evaluation.
Reply packets are sent as @code{struct mpc_rep_packet}", which has two fields:
@itemize @bullet
@item @code{uint32_t type}
@item @code{uint8_t val[]}
@end itemize
The type is checked to be the same as what is defined as "ret" in the
@code{pd} structure. The @code{val} array is a plain byte array that
is copied to the @code{ret} pointer passed to minipc_call (like
@i{result} in the sqrt example above). When @code{type} is an error
value (i.e. @code{MINIPC_ATYPE_ERROR}) the payload is a 4-byte errno
value. Note that @code{type} is not passed back to the caller of
@i{minipc_call}, because errors are identified by a negative return value.
@c ##########################################################################
@node Transport Mechanisms
@chapter Transport Mechanisms
The @i{name} argument used in either @i{minipc_server_create} or
@i{minipc_client_create} is used to select both the back-end transport
and instance. At this point in time, the library supports the
following back-ends for communication:
@table @i
@item Unix sockets
The default transport for communication is Unix-domain sockets
(@code{PF_UNIX} a.k.a. @code{PF_FILE} or @code{PF_LOCAL}). The
@i{name} argument is used as socket name within the default
directory for @i{mini-ipc} sockets. A server can
handle many clients at a time in a single thread, based on @i{select}.
@item System-V Shared memory
If the @i{name} argument is of the form @code{shm:<id>}, then
the transport being used is a shared memory area. The @i{id}
is either a decimal oh hex number (with leading @code{0x}).
In this case a server can only
accept one client, because there is no check for concurrency. Please
note that the library doesn't enforce this, so more clients may
be used if you make your own locking. @i{mini-ipc} places its
data structures at the beginning of the shared memory area.
@item I/O memory
If the @i{name} argument is of the form @code{mem:<address>},
the library will open @i{/dev/mem} and use the specific address
as communication transport. The address must be a multiple
of page size and written in hex form. This is meant to communicate with
a coprocessor living on and FPGA (this is one of the use cases
in the @i{White Rabbit Switch}). Again, both server and client
operation is supported, but a server should accept only one client
at a time.
@end table
Since the library is based on file descriptors, the two memory-based
transports fork a process that polls the memory area to signal
events on the @code{minipc_ch} file descriptor. The default polling
interval is 10ms, but it can be changed by calling @i{minipc_set_poll}
before creating the channel:
@example
int minipc_set_poll(int usec);
@end example
The function returns the previous polling interval, in microseconds,
or -1 with @code{errno} set to @code{-EINVAL} if the argument is
negative or zero. Please note that the polling interval must be set
before creating the channel, because the parameter will be used by the
child process.
@c ##########################################################################
@node Freestanding Operation
@chapter Freestanding Operation
This version of the library can be compiled for a @i{freestanding}
system that acts as a server for a hosted system. Our use case is an
LM32 soft-core that runs some real-time activities on request of the
main ARM processor.
The @i{Makefiles} of the package identify a @i{freestanding}
compilation environment by the @code{CROSS_COMPILE} prefix: if the
compiler has a dynamic C library, it is assumed to be hosted,
otherwise it's assumed to be freestanding. The user can force
freestanding compilation by passing @code{IPC_FREESTANDING}
in the environment or on the command line of @i{make}.
When compiling in a freestanding environment, only
@code{minipc-mem-server.c} is compiled. The source file includes
the usual @code{minipc.h} and @code{minipc-int.h}, but the code
itself is simpler (no sockets, no @code{/dev/mem} nor @i{shmget}).
The program has been tested (and is being used) on LM32, within an FPGA.
In a freestanding server, the following functions are implemented,
with some limitations:
@table @code
@item minipc_server_create
The function receives the usual arguments (name and flags),
but the name is mandated to be @code{mem:@i{XXXX}} where the
trailing part is an hex address. The data structure for
communication is stored at that address in CPU memory.
@item minipc_close
Works as expected, releasing resources. Note, however,
that only one server may be active at any time (so, you'll
most likely run a server at boot and won't ever close it).
@item minipc_export
The function exports one procedure that can be called
by the server. To void using @i{malloc}, the library uses
a static array of structures to host export information.
The length of the array is @code{MINIPC_MAX_EXPORT}
(12 by default).
@item minipc_unexport
The function frees one slot in the static array.
@item minipc_get_next_arg
The function works exactly like it does in hosted
environments
@item minipc_server_action
The function receives the same arguments as the equivalent
one in hosted environments, but doesn't honor the @i{timeout}
value. Timing is very device-specific, so the library can't
provide any. If a new request is pending, the function serves
it, otherwise it returns immediately.
@end table
In our use case, the compiler has @code{<strings.h>} and a few more
headers needed to compile the library, but lacks @code{<stdint.h>}.
As an ugly workaround, the file @code{arch-lm32/stdint.h}: the
@i{Makefile} adds @code{-Iarch-$(ARCH)} to the compiler's command
line, so we can build the library using @code{ARCH=lm32}. You may use
a similar trick to add missing headers for your freestanding
architecture.
@c ##########################################################################
@node Examples
@chapter Examples
The subdirectory @code{examples/} hosts a number of examples, that
are logically divided in groups.
@menu
* Trivial Example::
* Pty-based Example::
* A Bridge to Shared Memory::
* Native Shared Memory::
* Freestanding Server::
@end menu
@c ==========================================================================
@node Trivial Example
@section Trivial Example
The two programs in this group are called @code{trivial-server} and
@code{trivial-client}.
The server exports three functions:
@table @i
@item sum
The function receives two integers and returns another integer value.
@item gettimeofday
The function receives nothing and returns @code{struct timeval}.
@item sqrt
The functions receives one @code{double} number and returns another.
@end table
The server program shows how the @code{minipc_pd} structures are built
and how the @o{minipc_f} function is implemented. For each exported
function, the source file includes first the non-rpc-aware function
and then the mini-ipc wrapper.
In this case the main loop of the server just calls the server action,
without doing anything else. It is verbose in its own, and
declares @i{stderr} as @code{logfile} for the library.
The client declares the same @code{minipc_pd} structures as the server.
In more serious uses you should define the structure in a separate
source file, in order to share them in the server and client.
The client just calls the server functions a few times, delaying
half a second in between.
The following commands, run in two different terminal sessions,
can be used to test the pair of programs:
@example
./trivial-server
for n in $(seq 1 20); do
./trivial-client &
sleep 0.2
done
@end example
@c ==========================================================================
@node Pty-based Example
@section Pty-based Example
This set of programs sets up a more complete example. It shows how to
multiplex RPC operations and other activities by using th RPC @i{fd_set}
together with your own channels. The programs are called @code{pty-server}
and @code{pty-client}.
The server creates a pseudo-tty device and spawn a shell running in it.
If then feeds its own @i{stdin} to the shell and the shell's @i{stdout} to
its own @i{stdout}. You should call it with no arguments. Its log file
is called @code{/tmp/pty-server.XXXXXX}, with @code{XXXXXX} replaced
by some unique string by the @i{mkstemp} library function.
The RPC client can make queries to this server, without disturbing the
operation of the underlying shell. The client can ask for the
statistics of bytes read/written to/from the @i{pty} device; it can
read or write environment variables in the @code{pty-server}
application; it can feed strings to the shell driven by the pty
server; it can run strlen and strcat remotely, as well as remotely run
the stat system call (to check string and structure exchange). Each
@code{pty-client} run takes a command line, like the following ones
(names in angle-brackets are place holders):
@example
./pty-client count
./pty-client getenv <name>
./pty-client setenv <name> <value>
./pty-client feed <shell-command>
./pty-client strlen <string>
./pty-client strcat <string> <string>
./pty-client stat <filename>
@end example
The commands @code{count}, @code{feed}, @code{strlen}, @code{strcat}
and @code{stat} can be verified by just inspecting their own output. The
commands @code{setenv} and @code{getenv} may cross-verified.
Note that only one pty-server can be running at a time (as the rpc
socket is a static name within the socket directory), but you can run
several clients at the same time.
The sharing of @code{minipc_pd} (procedure description) in this set of
example is accomplished by linking the same object file in both
programs, to avoid code duplication.
@c ==========================================================================
@node A Bridge to Shared Memory
@section A Bridge to Shared Memory
This set of examples shows how to write a @i{bridge} from @i{mini-ipc}
to other communications media. Moreover, it shows how a single
process can act both as a client and a server.
The example example shows how to route @i{mini-ipc} requests to
another process through shared memory. Sure there is no real interest
in shared memory for IPC calls, but the same approach can be used with
mailbox-kind communication mechanisms to talk with hardware; for
example a CPU without OS running on an FPGA. That's why the examples
in this set are called @code{mbox-@i{something}}.
Please note that @i{mini-ipc} now has shared memory support: this set
of example doesn't use the library internals but implements a simpler
protocol.
This example-set is made up of three programs:
@table @code
@item mbox-bridge
The process acts as both a server and a client of the @i{mini-ipc}
protocol, routing requests to and from a shared-memory mailbox.
As @i{mini-ipc} client, it requests the current time to
@code{trivial-server} (such queries are originated from the shared
memory area). As @i{mini-ipc} server, it replies to
@i{stat} calls for local filenames (and such service is asked
to another process through the shared memory).
@item mbox-process
The "remote" process. It only communicating though
the mailbox device. It asks for the current time once a
second and can serve @i{stat} requests when they happen.
@item mbox-client
A program that reads @i{stdin} forever, asking a @i{stat}
system call for every line it reads (such @i{stat} is asked
to @code{mbox-bridge} using @i{mini-ipc}.
@end table
To demonstrate the mechanism, you'll need to run @code{trivial-server}
(which answers @i{timeofday} queries), @code{mbox-bridge} (to bridge
requests), @code{mbox-process} and one or more @code{mbox-client}
processes.
The following is an example out output generated by @code{mbox-bridge}
(which passes @i{gettimeofday} requests silently as a @i{mini-ipc}
client, but uses @i{mini-ipc} verbose logging to @i{stderr} as a server).
The server in the fragment below is being contacted by several clients,
some of which close the connection:
@example
minipc_export: exported 0x8a3d228 (stat) with pd 0x804a1a0
-- retval 00050058, args 00040004...
mpc_handle_connection: accept returned fd 5 (error 0)
mpc_handle_client: request for stat
mpc_handle_client: request for stat
mpc_handle_client: request for stat
mpc_handle_client: error 0 in fd 5, closing
mpc_handle_connection: accept returned fd 5 (error 0)
mpc_handle_client: request for stat
mpc_handle_client: request for stat
mpc_handle_client: error 0 in fd 5, closing
mpc_handle_connection: accept returned fd 5 (error 0)
mpc_handle_connection: accept returned fd 6 (error 0)
mpc_handle_client: request for stat
mpc_handle_client: error 0 in fd 6, closing
@end example
The following fragment shows the output of an @code{mbox-process},
which reports all actions (it asks the time and serves @i{stat}
queries):
@example
time: 1326231447.212400
time: 1326231448.218001
time: 1326231449.233714
Serving stat(/home/rubini)
time: 1326231450.240125
@end example
Finally, the following fragment shows the input line (first line)
and output lines reported by one invocation of @code{mbox-client}:
@example
/home/rubini
minipc_call: calling "stat"
/home/rubini:
mode 40755, size 32768, owner 410, atime 1326230999
@end example
@c ==========================================================================
@node Native Shared Memory
@section Native Shared Memory
This set of examples uses the native shared memory support in the
library. This means that server code exports functions using the
library data structures, and the client calls them in the same way.
There are two examples in this set, that share the source file that
instantiates function definitions. The code, as you may expect, is
mainly copied from the @i{pty} set of examples described earlier. I
see no point in factorizing stuff here, as the example must be
understandable by themselves.
@table @code
@item shmem-server
The program creates a server using a name @code{shm:@i{XXXX}} where
the whole identifier must be passed on the command line.
For example ``@code{./shmem-server shm:45}''. It uses verbose reporting
on @i{stderr} and just waits for requests. Passing @code{shm:}
on the command line is mandatory for consistency with the
corresponding client.
@item shmem-client
The program receives several command line arguments: the first
is the RPC channel identifier (e.g. @code{shm:45}); it should match
the identifier used by the corresponding server. If your server
lives in system memory (e.g., it is a freestanding server in
a soft-core), you can use this client, passing @code{mem:10003000}
or equivalent.
Further arguments are commands. The client
supports the following commands, that access all functions
exported by the server. Each of them receives 1 or 2 arguments:
@i{getenv}, @i{setenv}, @i{add}, @i{strlen}, @i{strcat}, @i{stat}.
@end table
This is a sample client session, with diagnostic stripped:
@example
$ ./shmem-client shm:45 getenv USER
getenv(USER) = rubini
$ ./shmem-client shm:45 setenv USER pippo
setenv USER = pippo : success
$ ./shmem-client shm:45 getenv USER
getenv(USER) = pippo
$ ./shmem-client shm:45 strlen "this is a test"
strlen(this is a test) = 14
$ ./shmem-client shm:45 add 666 999
add 666 + 999 = 1665 : success
@end example
At the same time, the server reported the following:
@example
mpc_handle_client: request for getenv
mpc_handle_client: request for setenv
mpc_handle_client: request for getenv
mpc_handle_client: request for strlen
mpc_handle_client: request for add
@end example
Remote errors are handled as usual: @code{EREMOTEIO} locally and the
proper @code{errno} value in the packet:
@example
$ ./shmem-client shm:45 stat /tmp/nosuchfile
minipc_call: remote error "No such file or directory"
.//shmem-client: remote "stat": Remote I/O error
@end example
@c ==========================================================================
@node Freestanding Server
@section Freestanding Server
When compiling for a freestanding CPU environment, the @i{Makefile}
only builds the example @code{freestanding-server} . This exports
two functions, @code{add} and @code{mul}.
You may use the @code{add} functionality from a @code{shmem-client}
example file, by passing the proper address in @code{/dev/mem}.
In order to run this example, however, you'll need to complete it
with all the missing stuff (drivers, linker scripts, an so on),
as well as fixing the address used for the communication data
structure.
Finally, please note that the library doesn't currently manage endian
conversion, and I'd love not to do that until I add IP channels (TCP
and UDP). However, your freestanding server may have a different
endianness from the host, so you may need the conversion.
But there are worse situations: your soft core may be big endian and
the host may be little endian, but using a straight 32-bit memory link
you won't notice endian issues when exchanging integers. That's great,
until you exchange strings: first of all, the bus just discards
byte-wide and halfword-wide accesses (the library does @i{memcpy} for
procedure names now, so this is mostly fixed); then, every word in the
string is byte-swapped. So you want @code{"add"} and you get
@code{"\0dda"} instead. You can work around the issue by using names
like @code{ssss}, @code{1111} and the like; ugly but simple -- fixing
this @i{cleanly} in the library is hard, I've no idea how to do it.
@c ##########################################################################
@node Bugs
@chapter Bugs
Please note that there are a number of untested corner cases as I write this.
There are no known bugs, but the following points at least may be
problematic as I didn't thoroughly test the code for them:
@itemize @bullet
@item There are some hard limits, like less than 64 file descriptors
open. The thing is meant to remain small and avoid some complexity.
@item Again, for simplicity, not everything is undone properly; but for
small systems with a well defined process-set this is not a problem.
For example, shared memory regions are not destroyed, and some
unlike errors don't undo everything that succeed before the error.
@item The shared memory thing has no locking at all, and the sequencing
engine is pretty simple-minded. The only expected user is a
real-time process running on a coprocessor with a single client
feeding it one request at a time.
@item Please don't try going near 1k argument/retval size
(@code{MINIPC_MAX_REPLY}: off-by-1 errors are most likely present (and
I have no tests yet to feel safe).
@item Strings passing may have some hidden bugs.
@item Struct passing may be faulty in some corner cases.
@end itemize
@c ##########################################################################
@bye
@c LocalWords: gnudd titlepage iftex texinfo CERN documentlanguage settitle
@c LocalWords: documentencoding setfilename afourpaper paragraphindent Tomasz
@c LocalWords: setchapternewpage finalout Wlostowski minipc struct uint ATYPE
@c LocalWords: retval enum strlen atype ASIZE args atypes sizeof const fileno
@c LocalWords: millisec errno recv ETIMEDOUT EREMOTEIO unexport timeoutms
@c LocalWords: fdset setptr endian itemx logf stderr logfile fprintf fflush
@c LocalWords: varargs unmarshalled unmarshalling unmarshall gettimeofday
@c LocalWords: timeval FPGA stat timeofday strncat usec EINVAL mkstemp atime
@c LocalWords: getenv setenv XXXXXX stdin stdout
trivial-server
trivial-client
pty-server
pty-client
mbox-bridge
mbox-client
mbox-process
shmem-server
shmem-client
freestanding-server
# examples for mini-rpc; depends on ..
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
CFLAGS = -Wall -ggdb -I.. -O2
LDFLAGS = -L.. -lminipc -lm
# we may be hosted or freestanding. For freestanding there is one
# example only. The following variables are exported by the upper
# Makefile, but if you "make" in this directory, it checks by itself
IPC_FREESTANDING ?= $(shell ../check-freestanding $(CC))
IPC_HOSTED ?= $(shell ../check-freestanding -n $(CC))
# Hosted is the opposite of freestanding, and cflags change too
ifeq ($(IPC_FREESTANDING),y)
IPC_HOSTED = n
CFLAGS += -ffreestanding -I../arch-$(ARCH)
else
IPC_HOSTED = y
endif
PROGS-$(IPC_HOSTED) = trivial-server trivial-client
PROGS-$(IPC_HOSTED) += pty-server pty-client
PROGS-$(IPC_HOSTED) += mbox-process mbox-bridge mbox-client
PROGS-$(IPC_HOSTED) += shmem-server shmem-client
IPC_FREESTANDING ?= n
PROGS-$(IPC_FREESTANDING) += freestanding-server
all: $(PROGS-y)
# the default puts LDFLAGS too early. Bah...
%: %.c
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
pty-server: pty-server.o pty-rpc_server.o pty-rpc_structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -lutil -o $@
pty-client: pty-client.o pty-rpc_structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
shmem-server: shmem-server.o shmem-structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
shmem-client: shmem-client.o shmem-structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
# This is stupid, it won't apply the first time, but, well... it works
$(PROGS-y) $(wildcard *.o): $(wildcard ../*.h ../*.a)
clean:
rm -f *.o *~ $(PROGS)
All documentation is now under the ../doc/ directory.
The input file is called doc/mini-ipc.in (it is readable by itself).
To have all usual output formats you can run "make" withing ./doc
but you need TexInfo installed.
/*
* Mini-ipc: an example freestanding server, based in memory
*
* Copyright (C) 2011,2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This code is copied from trivial-server, and made even more trivial
*/
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include "minipc.h"
/* A function that ignores the RPC and is written normally */
static int ss_do_sum(int a, int b)
{
return a + b;
}
/* The following ones are RPC-aware */
static int ss_sum_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int i;
i = ss_do_sum(args[0], args[1]);
*(int *)ret = i;
return 0;
}
static int ss_mul_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int a, b;
a = *(int *)args;
b = *(int *)(args + 1);
*(int *)ret = a * b;
return 0;
}
/* Describe the two functions above */
const struct minipc_pd ss_sum_struct = {
.f = ss_sum_function,
.name = "sum",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_mul_struct = {
.f = ss_mul_function,
.name = "mul",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *server;
server = minipc_server_create("mem:f000", 0);
if (!server)
return 1;
minipc_export(server, &ss_sum_struct);
minipc_export(server, &ss_mul_struct);
while (1) {
/* do something else... */
minipc_server_action(server, 1000);
}
}
/*
* Example bridge beteen mini-ipc and a mailbox memory area
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This example uses a shared memory area to talk with mbox-process.
* The same technique can be used to communicate with an os-less
* soft-core within an FPGA, and actually this is the reason why I'm
* writing this demo.
*
* The bridge can export functions in both ways: as a mini-ipc server
* it exports "stat", and as a client it calls gettimeofday in the
* trivial-server.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/select.h>
#include "minipc.h"
#include "minipc-shmem.h" /* The shared data structures */
struct minipc_mbox_info *info; /* unfortunately global... */
/* This function implements the "stat" mini-ipc server, by asking mbox */
static int mb_stat_server(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *fname = (void *)args;
/* pass this to the shmem */
memset(info->fname, 0, sizeof(info->fname));
strncpy(info->fname, fname, sizeof(info->fname) -1 );
/* ask and wait */
info->bridge_req = 1;
while (info->bridge_req)
usleep(1000);
memcpy(ret, &info->stbuf, sizeof(info->stbuf));
return 0;
}
/* The description here is the same as in the server */
const struct minipc_pd mb_tod_struct = {
.name = "gettimeofday",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct timeval),
.args = {
MINIPC_ARG_END,
},
};
/* And here it's the same as in the client */
struct minipc_pd mb_stat_struct = {
.f = mb_stat_server,
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* No arguments */
int main(int argc, char **argv)
{
struct minipc_ch *server;
struct minipc_ch *client;
void *shmem;
int ret;
if (argc > 1) {
fprintf(stderr, "%s: no arguments please\n", argv[0]);
exit(1);
}
/* Create your shared memory and/or attach to it */
ret = shmget(MINIPC_SHM, sizeof(struct minipc_mbox_info),
IPC_CREAT | 0666);
if (ret < 0) {
fprintf(stderr, "%s: shmget(): %s\n", argv[0],
strerror(errno));
exit(1);
}
shmem = shmat(ret, NULL, SHM_RND);
if (shmem == (void *)-1) {
fprintf(stderr, "%s: shmat(): %s\n", argv[0],
strerror(errno));
exit(1);
}
info = shmem;
/* Create a server socket for mini-ipc */
server = minipc_server_create("mbox", 0);
if (!server) {
fprintf(stderr, "%s: server_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(server, stderr);
minipc_export(server, &mb_stat_struct);
/* Connect as a client to the trivial-server */
client = minipc_client_create("trivial", 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Loop serving both mini-ipc and the mailbox */
while (1) {
fd_set set;
struct timeval to = {
MBOX_POLL_US / (1000*1000),
MBOX_POLL_US % (1000*1000)
};
minipc_server_get_fdset(server, &set);
/* Wait for any server, with the defined timeout */
ret = select(16 /* hack */, &set, NULL, NULL, &to);
if (ret > 0) {
if (minipc_server_action(server, 0) < 0) {
fprintf(stderr, "%s: server_action(): %s\n",
argv[0], strerror(errno));
exit(1);
}
}
/* No IPC request: if needed act as IPC client */
if (info->proc_req) {
memset(&info->tv, 0, sizeof(info->tv));
minipc_call(client, 100 /* ms */,
&mb_tod_struct, &info->tv, NULL);
info->proc_req = 0;
}
}
}
/*
* Example mbox client
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*
* This process requests "stat" calls to the mbox-bridge, which in turn
* asks it to the "remote" process that lives behing an mbox (shmem).
* The program reads filenames from stdin, in order to allow several clients
* connected to the same bridge.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "minipc.h"
#define CLIENT_TIMEOUT 100 /* ms */
/* The description here is the same as in the server */
struct minipc_pd rpc_stat = {
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *client;
struct stat stbuf;
int ret;
char s[80];
client = minipc_client_create("mbox", 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(client, stderr);
while (fgets(s, sizeof(s), stdin)) {
/* strip trailing newline */
s[strlen(s)-1] = '\0';
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_stat,
&stbuf, s);
if (ret < 0) {
fprintf(stderr, "stat(%s): %s\n", s, strerror(errno));
continue;
}
printf("%s:\n", s);
printf("mode %o, size %li, owner %i, atime %li\n",
stbuf.st_mode, (long)stbuf.st_size, stbuf.st_uid,
stbuf.st_atime);
}
return 0;
}
/*
* Example process connected to the world through a mailbox
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This example uses a shared memory area to talk with the mbox-bridge.
* In my real-world application this will be an os-less
* soft-core within an FPGA.
*
* The process is both a server and a client. As a server iIt replies
* to "stat" calls coming from the bridge process; as a client it asks
* the time of day once a second or so.
*
* It doesn't need minipc.h because it is not directly connected to the ipc
* mechanism
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "minipc-shmem.h" /* The shared data structures */
struct minipc_mbox_info *info; /* unfortunately global... */
/* No arguments */
int main(int argc, char **argv)
{
void *shmem;
int ret, i;
if (argc > 1) {
fprintf(stderr, "%s: no arguments please\n", argv[0]);
exit(1);
}
/* Create your shared memory and/or attach to it */
ret = shmget(MINIPC_SHM, sizeof(struct minipc_mbox_info),
IPC_CREAT | 0666);
if (ret < 0) {
fprintf(stderr, "%s: shmget(): %s\n", argv[0],
strerror(errno));
exit(1);
}
shmem = shmat(ret, NULL, SHM_RND);
if (shmem == (void *)-1) {
fprintf(stderr, "%s: shmat(): %s\n", argv[0],
strerror(errno));
exit(1);
}
info = shmem;
/* Loop forever */
for (i = 0; 1; i++) {
if (info->bridge_req) {
/* We got a request: serve it */
printf("Serving stat(%s)\n", info->fname);
memset(&info->stbuf, 0, sizeof(info->stbuf));
stat(info->fname, &info->stbuf);
info->bridge_req = 0;
}
if (i * MBOX_POLL_US >= 1000 * 1000) {
/* 1s passed, ask for the date */
info->proc_req = 1;
while (info->proc_req)
;
printf("time: %li.%06li\n", (long)info->tv.tv_sec,
(long)info->tv.tv_usec);
i = 0;
}
usleep(MBOX_POLL_US);
}
}
#ifndef __MINIPC_SHMEM_H__
#define __MINIPC_SHMEM_H__
#include <sys/stat.h>
#include <sys/time.h>
#define MINIPC_SHM 0x3465 /* Random */
struct minipc_mbox_info {
volatile int bridge_req; /* A request is asked by the bridge */
volatile int proc_req; /* A request is asked by the process */
/* The bridge can ask "stat" to the bare process */
char fname[64];
struct stat stbuf;
/* The bare process can ask timeofday to the bridge */
struct timeval tv;
};
#define MBOX_POLL_US (10*1000) /* 10ms */
#endif /* __MINIPC_SHMEM_H__ */
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "minipc.h"
#include "pty-server.h"
#define CLIENT_TIMEOUT 200 /* ms */
/* Retrieve the counters of the pty file descriptors */
static int do_count(struct minipc_ch *client, char **argv)
{
int ret;
struct pty_counts c;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_count, &c);
if (ret < 0)
return ret;
printf("counts: %i %i\n", c.in, c.out);
return 0;
}
/* getenv and setenv in the remote process */
static int do_getenv(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_getenv, buf, argv[2]);
if (ret < 0)
return ret;
printf("getenv(%s) = %s\n", argv[2], buf);
return 0;
}
static int do_setenv(struct minipc_ch *client, char **argv)
{
int ret, ret2;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_setenv, &ret2, argv[2],
argv[3]);
if (ret < 0)
return ret;
printf("setenv %s = %s : success\n", argv[2], argv[3]);
return 0;
}
/* send a string to the subshell of the server */
static int do_feed(struct minipc_ch *client, char **argv)
{
int ret, ret2;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_feed, &ret2, argv[2]);
if (ret < 0)
return ret;
printf("feeded %s : success\n", argv[2]);
return 0;
}
/* calculate a remote strlen */
static int do_strlen(struct minipc_ch *client, char **argv)
{
int ret, len;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strlen, &len, argv[2]);
if (ret < 0)
return ret;
printf("strlen(%s) = %i\n", argv[2], len);
return 0;
}
/* calculate a remote strcat */
static int do_strcat(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strcat, buf,
argv[2], argv[3]);
if (ret < 0)
return ret;
printf("strcat(%s,%s) = %s\n", argv[2], argv[3], buf);
return 0;
}
/* ask for a remote stat on a passed filename */
static int do_stat(struct minipc_ch *client, char **argv)
{
int ret;
struct stat stbuf;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_stat, &stbuf,
argv[2]);
if (ret < 0)
return ret;
printf("stat(\"%s\"):\n", argv[2]);
printf("dev %04x, inode %i, mode %o (rdev %04x), size %i\n",
(int)stbuf.st_dev, (int)stbuf.st_ino, stbuf.st_mode,
(int)stbuf.st_rdev, (int)stbuf.st_size);
return 0;
}
/*
* This is a parsing table for argv[1]
*/
struct {
char *name;
int (*f)(struct minipc_ch *client, char **argv);
int argc;
} *cp, calls[] = {
{ "count", do_count, 2},
{ "getenv", do_getenv, 3},
{ "setenv", do_setenv, 4},
{ "feed", do_feed, 3},
{ "strlen", do_strlen, 3},
{ "strcat", do_strcat, 4},
{ "stat", do_stat, 3},
{NULL, },
};
/*
* And this is the trivial main function -- more error checking than code
*/
int main(int argc, char **argv)
{
struct minipc_ch *client;
if (argc < 2) {
fprintf(stderr, "%s: use \"%s <cmd> [<arg> ...]\"\n",
argv[0], argv[0]);
exit(1);
}
client = minipc_client_create(PTY_RPC_NAME, 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* scan the table */
for (cp = calls; cp->name; cp++)
if (!strcmp(argv[1], cp->name))
break;
if (!cp->name) {
fprintf(stderr, "%s: no such command \"%s\"\n", argv[0],
argv[1]);
exit(1);
}
if (argc != cp->argc) {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
argv[0], argv[1]);
exit(1);
}
/* run the RPC we found */
if (cp->f(client, argv) < 0) {
fprintf(stderr, "%s: remote \"%s\": %s\n",
argv[0], argv[1], strerror(errno));
exit(1);
}
exit(0);
}
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file includes the RPC functions exported by the server.
* RPC-related stuff has been split to a different file for clarity.
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "minipc.h"
#include "pty-server.h"
static int saved_fdm; /* file descriptor of pty master, used by "feed" */
static struct pty_counts *saved_pc; /* counters, used by "count" */
/* Return the count accessing the pointer to the structure within main() */
static int pty_server_do_count(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
*(struct pty_counts *)ret = *saved_pc;
return 0;
}
/* Run getenv and setenv on behalf of the client */
static int pty_server_do_getenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *res = getenv(envname);
strcpy(ret, res); /* FIXME: max size */
return 0;
}
static int pty_server_do_setenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *envval;
args = minipc_get_next_arg(args, pd->args[0]);
envval = (void *)args;
setenv(envname, envval, 1);
return 0;
}
/* feed a string to the sub-shell (adding a newline) */
static int pty_server_do_feed(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *command = (void *)args;
int wrote;
wrote = write(saved_fdm, command, strlen(command));
wrote += write(saved_fdm, "\n", 1);
return 0;
}
/* calculate strlen for a string of the client */
static int pty_server_do_strlen(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s = (void *)args;
*(int *)ret = strlen(s);
return 0;
}
/* strcat two remote strings and pass the result back */
static int pty_server_do_strcat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s, *t;
char scat[256];
s = (void *)args;
args = minipc_get_next_arg(args, pd->args[0]);
t = (void *)args;
strncpy(scat, s, sizeof(scat));
strncat(scat, t, sizeof(scat) - strlen(s));
strcpy(ret, scat); /* FIXME: max size */
return 0;
}
/* stat a pathname received from the remote party */
static int pty_server_do_stat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
struct stat stbuf;
char *fname = (void *)args;
if (stat(fname, &stbuf) < 0)
return -1;
memcpy(ret, &stbuf, sizeof(stbuf));
return 0;
}
/*
* The following is called by the main function, and exports stuff.
* All the rest happens through callbacks from the library
*/
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
int pty_export_functions(struct minipc_ch *ch, int fdm, struct pty_counts *pc)
{
int i;
/* save data for the callbacks */
saved_fdm = fdm;
saved_pc = pc;
/* Use a handy table for the registration loop */
static struct {
struct minipc_pd *desc;
minipc_f *f;
} export_list [] = {
{&rpc_count, pty_server_do_count},
{&rpc_getenv, pty_server_do_getenv},
{&rpc_setenv, pty_server_do_setenv},
{&rpc_feed, pty_server_do_feed},
{&rpc_strlen, pty_server_do_strlen},
{&rpc_strcat, pty_server_do_strcat},
{&rpc_stat, pty_server_do_stat},
};
/*
* Complete the data structures by filling the function pointers
* and then register each of the exported procedures
*/
for (i = 0; i < ARRAY_SIZE(export_list); i++) {
export_list[i].desc->f = export_list[i].f;
if (minipc_export(ch, export_list[i].desc) < 0)
return -1;
}
return 0;
}
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file defines the data structures used to describe the RPC calls
* between server and client. Note that the function pointers are only
* instantiated in the server
*/
#include <sys/stat.h> /* for the structure */
#include "minipc.h"
#include "pty-server.h"
/* retrieve in and out counts from the pty-server */
struct minipc_pd rpc_count = {
.name = "count",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct pty_counts),
.args = {
MINIPC_ARG_END,
},
};
/* run getenv in the server */
struct minipc_pd rpc_getenv = {
.name = "getenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run setenv in the server */
struct minipc_pd rpc_setenv = {
.name = "setenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* feed a string to the underlying shell */
struct minipc_pd rpc_feed = {
.name = "feed",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* horribly run strlen and strcat */
struct minipc_pd rpc_strlen = {
.name = "strlen",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
struct minipc_pd rpc_strcat = {
.name = "strcat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run "stat" on a file name */
struct minipc_pd rpc_stat = {
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This is the pty-server. It only does the pty-master work, all RPC stuff
* has been confined to prt-rpc_server.c, which is called by our main loop
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pty.h>
#include <fcntl.h>
#include <utmp.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include "minipc.h"
#include "pty-server.h"
int main(int argc, char **argv)
{
int fdm, fds, pid, exitval = 0;
struct minipc_ch *ch;
struct pty_counts counters = {0,};
/* First, open the pty */
if (openpty(&fdm, &fds, NULL, NULL, NULL) < 0) {
fprintf(stderr, "%s: openpty(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Run a shell and let it go by itself before we open the rpc */
fprintf(stderr, "%s: Running a sub-shell in a new pty\n", argv[0]);
if ((pid = fork()) < 0) {
fprintf(stderr, "%s: fork(): %s\n", argv[0],
strerror(errno));
exit(1);
}
if (!pid) {
/* Child: become a shell and disappear... */
close(fdm);
login_tty(fds);
execl("/bin/sh", "sh", NULL);
fprintf(stderr, "%s: exec(/bin/sh): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Open the RPC server channel */
ch = minipc_server_create(PTY_RPC_NAME, 0);
if (!ch) {
fprintf(stderr, "%s: rpc_open(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Log file for diagnostics */
{
char name[] = "/tmp/pty-server.XXXXXX";
int logfd;
FILE *logf;
logfd = mkstemp(name);
if (logfd >= 0) {
logf = fdopen(logfd, "w");
if (logf)
minipc_set_logfile(ch, logf);
}
}
/* Register your functions: all our RPC is split to another source */
if (pty_export_functions(ch, fdm, &counters)) {
fprintf(stderr, "%s: exporting RPC functions: %s\n", argv[0],
strerror(errno));
exit(1);
}
/*
* Now, we must mirror stdin/stdout to the pty master, with RPC too.
* The first step is horribly changing the termios of our tty
*/
close(fds);
if (system("stty raw -echo") < 0) {
fprintf(stderr, "%s: can't run \"stty\"\n", argv[0]);
exit(1);
}
while (waitpid(pid, NULL, WNOHANG) != pid) {
fd_set set;
int nfd, i, j;
char buf[256];
/* ask the RPC engine its current fdset and augment it */
minipc_server_get_fdset(ch, &set);
FD_SET(STDIN_FILENO, &set);
FD_SET(fdm, &set);
/* wait for any of the FD to be active */
nfd = select(64 /* Hmmm... */, &set, NULL, NULL, NULL);
if (nfd < 0 && errno == EINTR)
continue;
if (nfd < 0) {
fprintf(stderr, "%s: select(): %s\n", argv[0],
strerror(errno));
exitval = 1;
break;
}
/* Handle fdm and fds by just mirroring stuff and counting */
if (FD_ISSET(STDIN_FILENO, &set)) {
i = read(0, buf, sizeof(buf));
if (i > 0) {
counters.in += i;
do {
j = write(fdm, buf, i);
i -= j;
} while (i && j >= 0);
}
nfd--;
}
if (FD_ISSET(fdm, &set)) {
i = read(fdm, buf, sizeof(buf));
if (i > 0) {
counters.out += i;
do {
j = write(1, buf, i);
i -= j;
} while (i && j >= 0);
}
nfd--;
}
/* If there are no more active fd, loop over */
if (!nfd)
continue;
/*
* If we are there, there has been an RPC call.
* We tell the library to use a 0 timeout, since we know
* for sure that at least one of its descriptors is pending.
*/
minipc_server_action(ch, 0);
}
/* The child shell exited, reset the tty and exit. Let RPC die out */
if (system("stty sane") < 0)
fprintf(stderr, "%s: can't restore tty settings\n", argv[0]);
exit(exitval);
}
#include "minipc.h"
struct pty_counts {
int in, out;
};
#define PTY_RPC_NAME "pty-server"
/* structures are in pty-rpc_structs.c */
extern struct minipc_pd
rpc_count, rpc_getenv, rpc_setenv, rpc_feed, rpc_strlen, rpc_strcat, rpc_stat;
/* The server needs to call out to a different file */
extern int pty_export_functions(struct minipc_ch *ch,
int fdm, struct pty_counts *pc);
/*
* Example mini-ipc client for shmem
* Mostly copied and adapted from pty-client.c
*
* Copyright (C) 2011,2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "minipc.h"
#include "shmem-structs.h"
#define CLIENT_TIMEOUT 200 /* ms */
/* getenv and setenv in the remote process */
static int do_getenv(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_getenv, buf, argv[2]);
if (ret < 0)
return ret;
printf("getenv(%s) = %s\n", argv[2], buf);
return 0;
}
static int do_setenv(struct minipc_ch *client, char **argv)
{
int ret, ret2;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_setenv, &ret2, argv[2],
argv[3]);
if (ret < 0)
return ret;
printf("setenv %s = %s : success\n", argv[2], argv[3]);
return 0;
}
static int do_add(struct minipc_ch *client, char **argv)
{
int ret;
int a = atoi(argv[2]);
int b = atoi(argv[3]);
int sum;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_add, &sum, a, b);
if (ret < 0)
return ret;
printf("add %i + %i = %i : success\n", a, b, sum);
return 0;
}
/* calculate a remote strlen */
static int do_strlen(struct minipc_ch *client, char **argv)
{
int ret, len;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strlen, &len, argv[2]);
if (ret < 0)
return ret;
printf("strlen(%s) = %i\n", argv[2], len);
return 0;
}
/* calculate a remote strcat */
static int do_strcat(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strcat, buf,
argv[2], argv[3]);
if (ret < 0)
return ret;
printf("strcat(%s,%s) = %s\n", argv[2], argv[3], buf);
return 0;
}
/* ask for a remote stat on a passed filename */
static int do_stat(struct minipc_ch *client, char **argv)
{
int ret;
struct stat stbuf;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_stat, &stbuf,
argv[2]);
if (ret < 0)
return ret;
printf("stat(\"%s\"):\n", argv[2]);
printf("dev %04x, inode %i, mode %o (rdev %04x), size %i\n",
(int)stbuf.st_dev, (int)stbuf.st_ino, stbuf.st_mode,
(int)stbuf.st_rdev, (int)stbuf.st_size);
return 0;
}
/*
* This is a parsing table for argv[1]
*/
struct {
char *name;
int (*f)(struct minipc_ch *client, char **argv);
int argc;
} *cp, calls[] = {
{ "getenv", do_getenv, 3},
{ "setenv", do_setenv, 4},
{ "add", do_add, 4},
{ "strlen", do_strlen, 3},
{ "strcat", do_strcat, 4},
{ "stat", do_stat, 3},
{NULL, },
};
/*
* And this is the trivial main function -- more error checking than code
*/
int main(int argc, char **argv)
{
struct minipc_ch *client;
if (argc <= 2) {
fprintf(stderr, "%s: use \"%s <shmid> <cmd> [<arg> ...]\n",
argv[0], argv[0]);
exit(1);
}
client = minipc_client_create(argv[1], 0);
if (!client) {
fprintf(stderr, "%s: rpc_open(%s): %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
minipc_set_logfile(client, stderr);
argv[1] = argv[0]; argv++; argc--;
/* scan the table */
for (cp = calls; cp->name; cp++)
if (!strcmp(argv[1], cp->name))
break;
if (!cp->name) {
fprintf(stderr, "%s: no such command \"%s\"\n", argv[0],
argv[1]);
exit(1);
}
if (argc != cp->argc) {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
argv[0], argv[1]);
exit(1);
}
/* run the RPC we found */
if (cp->f(client, argv) < 0) {
fprintf(stderr, "%s: remote \"%s\": %s\n",
argv[0], argv[1], strerror(errno));
exit(1);
}
exit(0);
}
/*
* Example mini-ipc server for shmem
* (mostly copied from the pty server code)
*
* Copyright (C) 2011-2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file includes the RPC functions exported by the server.
* RPC-related stuff has been split to a different file for clarity.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "minipc.h"
#include "shmem-structs.h"
/* Run getenv and setenv on behalf of the client */
static int shm_server_do_getenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *res = getenv(envname);
strcpy(ret, res); /* FIXME: max size */
return 0;
}
static int shm_server_do_setenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *envval;
args = minipc_get_next_arg(args, pd->args[0]);
envval = (void *)args;
setenv(envname, envval, 1);
return 0;
}
/* add two integer numbers */
static int shm_server_do_add(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int a, b, sum;
a = args[0];
args = minipc_get_next_arg(args, pd->args[0]);
b = args[0];
sum = a + b;
*(int *)ret = sum;
return 0;
}
/* calculate strlen for a string of the client */
static int shm_server_do_strlen(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s = (void *)args;
*(int *)ret = strlen(s);
return 0;
}
/* strcat two remote strings and pass the result back */
static int shm_server_do_strcat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s, *t;
char scat[256];
s = (void *)args;
args = minipc_get_next_arg(args, pd->args[0]);
t = (void *)args;
strncpy(scat, s, sizeof(scat));
strncat(scat, t, sizeof(scat) - strlen(s));
strcpy(ret, scat); /* FIXME: max size */
return 0;
}
/* stat a pathname received from the remote party */
static int shm_server_do_stat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
struct stat stbuf;
char *fname = (void *)args;
if (stat(fname, &stbuf) < 0)
return -1;
memcpy(ret, &stbuf, sizeof(stbuf));
return 0;
}
/*
* The following is called by the main function, and exports stuff.
* All the rest happens through callbacks from the library
*/
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
int shm_export_functions(struct minipc_ch *ch)
{
int i;
/* Use a handy table for the registration loop */
static struct {
struct minipc_pd *desc;
minipc_f *f;
} export_list [] = {
{&rpc_getenv, shm_server_do_getenv},
{&rpc_setenv, shm_server_do_setenv},
{&rpc_add, shm_server_do_add},
{&rpc_strlen, shm_server_do_strlen},
{&rpc_strcat, shm_server_do_strcat},
{&rpc_stat, shm_server_do_stat},
};
/*
* Complete the data structures by filling the function pointers
* and then register each of the exported procedures
*/
for (i = 0; i < ARRAY_SIZE(export_list); i++) {
export_list[i].desc->f = export_list[i].f;
if (minipc_export(ch, export_list[i].desc) < 0)
return -1;
}
return 0;
}
/* the main function only runs the server */
int main(int argc, char **argv)
{
struct minipc_ch *ch;
if (argc != 2) {
fprintf(stderr, "%s: use \"%s <shmid>\n", argv[0], argv[0]);
exit(1);
}
ch = minipc_server_create(argv[1], 0);
if (!ch) {
fprintf(stderr, "%s: rpc_open(%s): %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
minipc_set_logfile(ch, stderr);
/* Register your functions */
if (shm_export_functions(ch)) {
fprintf(stderr, "%s: exporting RPC functions: %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Then just obey requests */
while (1) {
minipc_server_action(ch, 1000);
}
}
/*
* Structures for example shmem mini-ipc backends
*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file defines the data structures used to describe the RPC calls
* between server and client. Note that the function pointers are only
* instantiated in the server
*/
#include "minipc.h"
#include "shmem-structs.h"
#include <sys/stat.h>
/* run getenv in the server */
struct minipc_pd rpc_getenv = {
.name = "getenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run setenv in the server */
struct minipc_pd rpc_setenv = {
.name = "setenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* addition of two integers */
struct minipc_pd rpc_add = {
.name = "add",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
/* horribly run strlen and strcat */
struct minipc_pd rpc_strlen = {
.name = "strlen",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
struct minipc_pd rpc_strcat = {
.name = "strcat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run "stat" on a file name */
struct minipc_pd rpc_stat = {
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
extern struct minipc_pd rpc_getenv;
extern struct minipc_pd rpc_setenv;
extern struct minipc_pd rpc_add;
extern struct minipc_pd rpc_strlen;
extern struct minipc_pd rpc_strcat;
extern struct minipc_pd rpc_stat;
/*
* Example mini-ipc client
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include "minipc.h"
#define TRIVIAL_TIMEOUT 100 /* ms */
/* The description here is the same as in the server */
const struct minipc_pd ss_sum_struct = {
.name = "sum",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_tod_struct = {
.name = "gettimeofday",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct timeval),
.args = {
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_sqrt_struct = {
.name = "sqrt",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *client;
int a, b, c, ret;
struct timeval tv;
double rt_in, rt_out;
client = minipc_client_create("trivial", 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(client, stderr);
/*
* gettod, sum, sum, gettod
* pause a while in-between, so several clients can be run
* concurrently as a load test on the server
*/
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_tod_struct, &tv, NULL);
if (ret < 0) {
goto error;
}
printf("tv: %li.%06li\n", tv.tv_sec, tv.tv_usec);
usleep(500*1000);
a = 345; b = 628;
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_sum_struct, &c, a, b);
if (ret < 0) {
goto error;
}
printf("%i + %i = %i\n", a, b, c);
usleep(500*1000);
a = 10; b = 20;
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_sum_struct, &c, a, b);
if (ret < 0) {
goto error;
}
printf("%i + %i = %i\n", a, b, c);
usleep(500*1000);
rt_in = 2.0;
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_sqrt_struct, &rt_out, rt_in);
if (ret < 0) {
goto error;
}
printf("sqrt(%lf) = %lf\n", rt_in, rt_out);
usleep(500*1000);
return 0;
error:
fprintf(stderr, "Error in rpc: %s\n", strerror(errno));
exit(1);
}
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include "minipc.h"
/* A function that ignores the RPC and is written normally */
static int ss_do_sum(int a, int b)
{
return a + b;
}
/* The following ones are RPC-aware */
static int ss_sum_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int i;
i = ss_do_sum(args[0], args[1]);
*(int *)ret = i;
return 0;
}
static int ss_tod_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
struct timeval tv;
gettimeofday(&tv, NULL);
*(struct timeval *)ret = tv;
return 0;
}
static int ss_sqrt_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
double input, output;
input = *(double *)args;
output = sqrt(input);
*(double *)ret = output;
return 0;
}
/* Describe the three functions above */
const struct minipc_pd ss_sum_struct = {
.f = ss_sum_function,
.name = "sum",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_tod_struct = {
.f = ss_tod_function,
.name = "gettimeofday",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct timeval),
.args = {
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_sqrt_struct = {
.f = ss_sqrt_function,
.name = "sqrt",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *server;
server = minipc_server_create("trivial", 0);
if (!server) {
fprintf(stderr, "%s: server_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(server, stderr);
minipc_export(server, &ss_sum_struct);
minipc_export(server, &ss_tod_struct);
minipc_export(server, &ss_sqrt_struct);
while (1) {
if (minipc_server_action(server, 1000) < 0) {
fprintf(stderr, "%s: server_action(): %s\n", argv[0],
strerror(errno));
exit(1);
}
fprintf(stdout, "%s: looping...\n", __func__);
}
}
/*
* Mini-ipc: Exported functions for client operation
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <poll.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <sys/socket.h>
#include "minipc-int.h"
struct minipc_ch *minipc_client_create(const char *name, int f)
{
return __minipc_link_create(name, MPC_USER_FLAGS(f) | MPC_FLAG_CLIENT);
}
int minipc_call(struct minipc_ch *ch, int millisec_timeout,
const struct minipc_pd *pd, void *ret, ...)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_shmem *shm = link->memaddr;
struct pollfd pfd;
int i, narg, size, retsize, pollnr;
int atype, asize;
va_list ap;
struct mpc_req_packet *p_out, _pkt_out = {"",};
struct mpc_rep_packet *p_in, _pkt_in;
CHECK_LINK(link);
if (shm) {
p_out = &shm->request;
p_in = &shm->reply;
} else {
p_out = & _pkt_out;
p_in = & _pkt_in;
}
/* Build the packet to send out -- marshall args */
if (link->logf) {
fprintf(link->logf, "%s: calling \"%s\"\n",
__func__, pd->name);
}
memcpy(p_out->name, pd->name, MINIPC_MAX_NAME);
va_start(ap, ret);
for (i = narg = 0; ; i++) {
int next_narg = narg;
atype = MINIPC_GET_ATYPE(pd->args[i]);
asize = MINIPC_GET_ASIZE(pd->args[i]);
next_narg += MINIPC_GET_ANUM(asize);
if (next_narg >= MINIPC_MAX_ARGUMENTS) /* unlikely */
goto doesnt_fit;
switch (atype) {
case MINIPC_ATYPE_NONE:
goto out; /* end of list */
case MINIPC_ATYPE_INT:
p_out->args[narg++] = va_arg(ap, int);
break;
case MINIPC_ATYPE_INT64:
*(uint64_t *)(p_out->args + narg)
= va_arg(ap, uint64_t);
narg = next_narg;
break;
case MINIPC_ATYPE_DOUBLE:
*(double *)(p_out->args + narg) = va_arg(ap, double);
narg = next_narg;
break;
case MINIPC_ATYPE_STRING:
{
char *sin = va_arg(ap, void *);
char *sout = (void *)(p_out->args + narg);
int slen = strlen(sin);
int alen;
/*
* argument len is arbitrary, terminate and 4-align
* we can't use next_narg here, as len changes
*/
alen = MINIPC_GET_ANUM(slen + 1);
if (narg + alen >= MINIPC_MAX_ARGUMENTS)
goto doesnt_fit;
strcpy(sout, sin);
narg += alen;
break;
}
case MINIPC_ATYPE_STRUCT:
memcpy(p_out->args + narg, va_arg(ap, void *), asize);
narg = next_narg;
break;
default:
if (link->logf) {
fprintf(link->logf, "%s: unkown type 0x%x\n",
__func__, atype);
}
errno = EPROTO;
return -1;
}
}
out:
va_end(ap);
if (!shm)
{
size = sizeof(p_out->name) + sizeof(p_out->args[0]) * narg;
if (send(ch->fd, p_out, size, 0) < 0) {
/* errno already set */
return -1;
}
}
if (shm) {
if ( mpc_mem_request(ch, millisec_timeout ) < 0 ) // timeout or other shit
return -1;
size = retsize = sizeof(shm->reply);
} else {
/* Wait for the reply packet */
pfd.fd = ch->fd;
pfd.events = POLLIN | POLLHUP;
pfd.revents = 0;
pollnr = poll(&pfd, 1, millisec_timeout);
if (pollnr < 0) {
/* errno already set */
return -1;
}
if (pollnr == 0) {
errno = ETIMEDOUT;
return -1;
}
/* this "size" is wrong for strings, so recv the max size */
size = MINIPC_GET_ASIZE(pd->retval) + sizeof(uint32_t);
retsize = recv(ch->fd, p_in, sizeof(*p_in), 0);
}
/* if very short, we have a problem */
if (retsize < (sizeof(p_in->type)) + sizeof(int))
goto too_short;
/* remote error reported */
if (MINIPC_GET_ATYPE(p_in->type) == MINIPC_ATYPE_ERROR) {
int remoteerr = *(int *)&p_in->val;
if (link->logf) {
fprintf(link->logf, "%s: remote error \"%s\"\n",
__func__, strerror(remoteerr));
}
*(int *)ret = remoteerr;
errno = EREMOTEIO;
return -1;
}
/* another check: the return type must match */
if (MINIPC_GET_ATYPE(p_in->type) != MINIPC_GET_ATYPE(pd->retval)) {
if (link->logf) {
fprintf(link->logf, "%s: wrong code %08x (not %08x)\n",
__func__, p_in->type, pd->retval);
}
errno = EPROTO;
return -1;
}
/* check size */
if (retsize < size)
goto too_short;
/* all good */
memcpy(ret, &p_in->val, MINIPC_GET_ASIZE(p_in->type));
return 0;
too_short:
if (link->logf) {
fprintf(link->logf, "%s: short reply (%i bytes)\n",
__func__, retsize);
}
errno = EPROTO;
return -1;
doesnt_fit:
if (link->logf) {
fprintf(link->logf, "%s: rpc call \"%s\" won't fit %i slots\n",
__func__, pd->name, MINIPC_MAX_ARGUMENTS);
}
errno = EPROTO;
return -1;
}
/*
* Mini-ipc: Core library functions and data
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* Update from Tom: these are not my ideas. This is fucking stupid beardy '70s UNIX code
* because its' author didn't like assembly.
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include "minipc-int.h"
struct mpc_link *__mpc_base;
static int __mpc_poll_usec = MINIPC_DEFAULT_POLL;
void mpc_free_flist(struct mpc_link *link, struct mpc_flist *flist)
{
struct mpc_flist **nextp;
/* Look for flist and release it*/
for (nextp = &link->flist; (*nextp); nextp = &(*nextp)->next)
if (*nextp == flist)
break;
if (!*nextp) {
if (link->logf)
fprintf(link->logf, "%s: function not found %p (%s)\n",
__func__, flist, flist->pd->name);
return;
}
*nextp = flist->next;
if (link->logf)
fprintf(link->logf, "%s: unexported function %p (%s)\n",
__func__, flist->pd->f, flist->pd->name);
free(flist);
}
int minipc_close(struct minipc_ch *ch)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_link **nextp;
CHECK_LINK(link);
/* Look for link in our list */
for (nextp = &__mpc_base; (*nextp); nextp = &(*nextp)->nextl)
if (*nextp == link)
break;
if (!*nextp) {
errno = ENOENT;
return -1;
}
(*nextp)->nextl = link->nextl;
if (link->logf) {
fprintf(link->logf, "%s: found link %p (fd %i)\n",
__func__, link, link->ch.fd);
}
if(ch->fd > 0)
close(ch->fd);
if (link->flags & MPC_FLAG_SHMEM)
shmdt(link->memaddr);
if (link->flags & MPC_FLAG_DEVMEM)
munmap(link->memaddr, link->memsize);
/* Release allocated functions */
while (link->flist)
mpc_free_flist(link, link->flist);
free(link);
return 0;
}
int minipc_set_poll(int usec)
{
int ret = __mpc_poll_usec;
if (usec <= 0) {
errno = EINVAL;
return -1;
}
__mpc_poll_usec = usec;
return ret;
}
int minipc_set_logfile(struct minipc_ch *ch, FILE *logf)
{
struct mpc_link *link = mpc_get_link(ch);
CHECK_LINK(link);
link->logf = logf;
return 0;
}
/* helper function for memory-based channels */
static struct mpc_link *__minipc_memlink_create(struct mpc_link *link)
{
void *addr = 0;
long offset;
int memsize, ret;
int pagesize = getpagesize();
memsize = (sizeof(struct mpc_shmem) + pagesize - 1) & ~pagesize;
/* Warning: no check for trailing garbage in name */
if (sscanf(link->name, "shm:%li", &offset)) {
ret = shmget(offset, memsize, IPC_CREAT | 0666);
if (ret < 0)
return NULL;
addr = shmat(ret, NULL, SHM_RND);
if (addr == (void *)-1)
return NULL;
link->flags |= MPC_FLAG_SHMEM;
}
/* Warning: no check for trailing garbage in name -- hex mandatory */
if (sscanf(link->name, "mem:%lx", &offset)) {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0)
return NULL;
addr = mmap(0, memsize, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, offset);
close(fd);
if (addr == (MAP_FAILED))
return NULL;
link->flags |= MPC_FLAG_DEVMEM;
}
link->memaddr = addr;
link->memsize = memsize;
if (link->flags & MPC_FLAG_SERVER)
memset(addr, 0, sizeof(struct mpc_shmem));
return link;
err_unmap:
if (link->flags & MPC_FLAG_SHMEM)
shmdt(link->memaddr);
if (link->flags & MPC_FLAG_DEVMEM)
munmap(link->memaddr, link->memsize);
return NULL;
}
/* create a link, either server or client */
struct minipc_ch *__minipc_link_create(const char *name, int flags)
{
struct mpc_link *link;
struct sockaddr_un sun;
int fd, i;
link = calloc(1, sizeof(*link));
if (!link) return NULL;
link->magic = MPC_MAGIC;
link->flags = flags;
link->last_request_id = 0;
strncpy(link->name, name, sizeof(link->name) -1);
/* special-case the memory-based channels */
if (!strncmp(name, "shm:", 4) || !strncmp(name, "mem:", 4)) {
if (!__minipc_memlink_create(link))
goto out_free;
goto out_success;
}
/* now create the socket and prepare the service */
fd = socket(SOCK_STREAM, AF_UNIX, 0);
if(fd < 0)
goto out_free;
link->ch.fd = fd;
sun.sun_family = AF_UNIX;
strcpy(sun.sun_path, MINIPC_BASE_PATH);
strcat(sun.sun_path, "/");
strcat(sun.sun_path, link->name);
mkdir(MINIPC_BASE_PATH, 0777); /* may exist, ignore errors */
if (flags & MPC_FLAG_SERVER) {
unlink(sun.sun_path);
if (bind (fd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
goto out_close;
if (listen(fd, 5) < 0)
goto out_close;
} else { /* client */
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
goto out_close;
}
/* success: fix your fd values, link to the list and return */
out_success:
if (flags & MPC_FLAG_SERVER) {
for (i = 0; i < MINIPC_MAX_CLIENTS; i++)
link->fd[i] = -1;
FD_ZERO(&link->fdset);
FD_SET(link->ch.fd, &link->fdset);
}
link->addr = sun;
link->nextl = __mpc_base;
__mpc_base = link;
return &link->ch;
out_close:
close(fd);
out_free:
free(link);
return NULL;
}
/*
* Private definition for mini-ipc
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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.
*/
#ifndef __MINIPC_INT_H__
#define __MINIPC_INT_H__
#include <sys/types.h>
#if __STDC_HOSTED__ /* freestanding servers have less material */
#include <sys/un.h>
#include <sys/select.h>
#endif
#include "minipc.h"
/* be safe, in case some other header had them slightly differntly */
#undef container_of
#undef offsetof
#undef ARRAY_SIZE
/* We are strongly based on container_of internally */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/*
* While public symbols are minipc_* internal ones are mpc_* to be shorter.
* The connection includes an fd, which is the only thing returned back
*/
/* The list of functions attached to a service */
struct mpc_flist {
const struct minipc_pd *pd;
struct mpc_flist *next;
};
/*
* The main server or client structure. Server links have client sockets
* hooking on it.
*/
struct mpc_link {
struct minipc_ch ch;
int magic;
int flags;
struct mpc_link *nextl;
struct mpc_flist *flist;
void *memaddr;
int memsize;
uint32_t last_request_id;
#if __STDC_HOSTED__ /* these fields are not used in freestanding uC */
FILE *logf;
struct sockaddr_un addr;
int fd[MINIPC_MAX_CLIENTS];
fd_set fdset;
#endif
char name[MINIPC_MAX_NAME];
};
#define MPC_MAGIC 0xc0ffee99
#define MPC_FLAG_SERVER 0x00010000
#define MPC_FLAG_CLIENT 0x00020000
#define MPC_FLAG_SHMEM 0x00040000
#define MPC_FLAG_DEVMEM 0x00080000
#define MPC_USER_FLAGS(x) ((x) & 0xffff)
/* The request packet being transferred */
struct mpc_req_packet {
char name[MINIPC_MAX_NAME];
uint32_t args[MINIPC_MAX_ARGUMENTS];
};
/* The reply packet being transferred */
struct mpc_rep_packet {
uint32_t type;
uint8_t val[MINIPC_MAX_REPLY];
};
/* A structure for shared memory (takes more than 2kB) */
struct mpc_shmem {
uint32_t nrequest; /* incremented at each request */
uint32_t nreply; /* incremented at each reply */
struct mpc_req_packet request;
struct mpc_rep_packet reply;
};
#define MPC_TIMEOUT 1000 /* msec, hardwired */
static inline struct mpc_link *mpc_get_link(struct minipc_ch *ch)
{
return container_of(ch, struct mpc_link, ch);
}
#define CHECK_LINK(link) /* Horrible shortcut, don't tell around... */ \
if ((link)->magic != MPC_MAGIC) { \
errno = EINVAL; \
return -1; \
}
extern struct mpc_link *__mpc_base;
extern void mpc_free_flist(struct mpc_link *link, struct mpc_flist *flist);
extern int mpc_mem_request(struct minipc_ch *ch, int timeout_ms);
extern struct minipc_ch *__minipc_link_create(const char *name, int flags);
/* Used for lists and structures -- sizeof(uint32_t) is 4, is it? */
#define MINIPC_GET_ANUM(len) (((len) + 3) >> 2)
#endif /* __MINIPC_INT_H__ */
/*
* Mini-ipc: Exported functions for freestanding server
*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This replicates some code of minipc-core and minipc-server.
* It implements the functions needed to make a freestanding server
* (for example, an lm32 running on an FPGA -- the case I actually need).
*/
#include "minipc-int.h"
#include <string.h>
#include <sys/errno.h>
/* HACK: use a static link, one only */
static struct mpc_link __static_link;
/* The create function just picks an hex address from the name "mem:AABBCC" */
struct minipc_ch *minipc_server_create(const char *name, int flags)
{
struct mpc_link *link = &__static_link;
int i, c, addr = 0;
int memsize = sizeof(struct mpc_shmem);
if (link->magic) {
errno = EBUSY;
return NULL;
}
/* Most code from __minipc_link_create and __minipc_memlink_create */
flags |= MPC_FLAG_SERVER;
if (strncmp(name, "mem:", 4)) {
errno = EINVAL;
return NULL;
}
/* Ok, valid name. Hopefully */
link->magic = MPC_MAGIC;
link->flags = flags;
strncpy(link->name, name, sizeof(link->name) -1);
/* Parse the hex address */
for (i = 4; (c = name[i]); i++) {
addr *= 0x10;
if (c >= '0' && c <= '9') addr += c - '0';
if (c >= 'a' && c <= 'f') addr += c - 'a' + 10;
if (c >= 'A' && c <= 'F') addr += c - 'A' + 10;
}
link->flags |= MPC_FLAG_SHMEM; /* needed? */
link->memaddr = (void *)addr;
link->memsize = memsize;
if (link->flags & MPC_FLAG_SERVER)
memset(link->memaddr, 0, memsize);
return &link->ch;
}
/* Close only marks the link as available */
int minipc_close(struct minipc_ch *ch)
{
struct mpc_link *link = mpc_get_link(ch);
CHECK_LINK(link);
link->magic = 0; /* available */
return 0;
}
/* HACK: use a static array of flist, to avoid malloc and free */
static struct mpc_flist __static_flist[MINIPC_MAX_EXPORT];
static void *calloc(size_t unused, size_t unused2)
{
int i;
struct mpc_flist *p;
for (p = __static_flist, i = 0; i < MINIPC_MAX_EXPORT; p++, i++)
if (!p->pd)
break;
if (i == MINIPC_MAX_EXPORT) {
errno = ENOMEM;
return NULL;
}
return p;
}
static void free(void *ptr)
{
struct mpc_flist *p = ptr;
p->pd = NULL;
}
/* From: minipc-core.c, but relying on fake free above */
void mpc_free_flist(struct mpc_link *link, struct mpc_flist *flist)
{
struct mpc_flist **nextp;
/* Look for flist and release it*/
for (nextp = &link->flist; (*nextp); nextp = &(*nextp)->next)
if (*nextp == flist)
break;
if (!*nextp) {
return;
}
*nextp = flist->next;
free(flist);
}
/* From: minipc-server.c -- but no log and relies on fake calloc above */
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
flist = calloc(1, sizeof(*flist));
if (!flist)
return -1;
flist->pd = pd;
flist->next = link->flist;
link->flist = flist;
return 0;
}
/* From: minipc-server.c -- but no log file */
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
/* We must find the flist that points to pd */
for (flist = link->flist; flist; flist = flist->next)
if (flist->pd == pd)
break;
if (!flist) {
errno = EINVAL;
return -1;
}
flist = container_of(&pd, struct mpc_flist, pd);
mpc_free_flist(link, flist);
return 0;
}
/* From: minipc-server.c */
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype)
{
int asize;
char *s = (void *)arg;
if (MINIPC_GET_ATYPE(atype) != MINIPC_ATYPE_STRING)
asize = MINIPC_GET_ANUM(MINIPC_GET_ASIZE(atype));
else
asize = MINIPC_GET_ANUM(strlen(s) + 1);
return arg + asize;
}
/* From: minipc-server.c (mostly: mpc_handle_client) */
int minipc_server_action(struct minipc_ch *ch, int timeoutms)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_req_packet *p_in;
struct mpc_rep_packet *p_out;
struct mpc_shmem *shm = link->memaddr;
const struct minipc_pd *pd;
struct mpc_flist *flist;
int i;
CHECK_LINK(link);
/* keep track of the request in an otherwise unused field */
if (shm->nrequest == link->last_request_id)
return 0;
link->last_request_id = shm->nrequest;
p_in = &shm->request;
p_out = &shm->reply;
/* use p_in->name to look for the function */
for (flist = link->flist; flist; flist = flist->next)
if (!(strcmp(p_in->name, flist->pd->name)))
break;
if (!flist) {
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = EOPNOTSUPP;
goto send_reply;
}
pd = flist->pd;
/* call the function and send back stuff */
i = pd->f(pd, p_in->args, p_out->val);
if (i < 0) {
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = errno;
} else {
/* Use retval, but fix the length for strings */
if (MINIPC_GET_ATYPE(pd->retval) == MINIPC_ATYPE_STRING) {
int size = strlen((char *)p_out->val) + 1;
size = (size + 3) & ~3; /* align */
p_out->type =
__MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, size);
} else {
p_out->type = pd->retval;
}
}
send_reply:
shm->nreply++; /* message already in place */
return 0;
}
/*
* Mini-ipc: Exported functions for server operation
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas and code by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <time.h>
#include "minipc-int.h"
/*
* This function creates a server structure and links it to the
* process-wide list of links
*/
struct minipc_ch *minipc_server_create(const char *name, int f)
{
return __minipc_link_create(name, MPC_USER_FLAGS(f) | MPC_FLAG_SERVER);
}
/*
* The following ones add to the export list and remove from it
*/
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
flist = calloc(1, sizeof(*flist));
if (!flist)
return -1;
flist->pd = pd;
flist->next = link->flist;
link->flist = flist;
if (link->logf)
fprintf(link->logf, "%s: exported %p (%s) with pd %p --"
" retval %08x, args %08x...\n", __func__,
flist, pd->name, pd, pd->retval, pd->args[0]);
return 0;
}
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
/* We must find the flist that points to pd */
for (flist = link->flist; flist; flist = flist->next)
if (flist->pd == pd)
break;
if (!flist) {
if (link->logf)
fprintf(link->logf, "%s: not found pd %p\n",
__func__, pd);
errno = EINVAL;
return -1;
}
flist = container_of(&pd, struct mpc_flist, pd);
mpc_free_flist(link, flist);
return 0;
}
/* Return the current fdset associated to the service */
int minipc_server_get_fdset(struct minipc_ch *ch, fd_set *setptr)
{
struct mpc_link *link = mpc_get_link(ch);
CHECK_LINK(link);
*setptr = link->fdset;
return 0;
}
/* Get a pointer to the next argument in the array */
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype)
{
int asize;
char *s = (void *)arg;
if (MINIPC_GET_ATYPE(atype) != MINIPC_ATYPE_STRING)
asize = MINIPC_GET_ANUM(MINIPC_GET_ASIZE(atype));
else
asize = MINIPC_GET_ANUM(strlen(s) + 1);
return arg + asize;
}
/*
* Internal functions used by server action below: handle a request
* or the arrival of a new client
*/
static void mpc_handle_client(struct minipc_ch *ch, int pos, int fd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_req_packet *p_in, _pkt_in;
struct mpc_rep_packet *p_out, _pkt_out;
struct mpc_shmem *shm = link->memaddr;
const struct minipc_pd *pd;
struct mpc_flist *flist;
int i;
if (shm) {
p_in = &shm->request;
p_out = &shm->reply;
/* read one byte, it's just a signal */
mpc_mem_request( ch, -1 );
} else {
p_in = & _pkt_in;
p_out = & _pkt_out;
/* receive the packet and manage errors */
i = recv(fd, p_in, sizeof(*p_in), 0);
if (i < 0 && errno == EINTR)
return;
if (i <= 0)
goto close_client;
}
/* use p_in->name to look for the function */
for (flist = link->flist; flist; flist = flist->next)
if (!(strcmp(p_in->name, flist->pd->name)))
break;
if (!flist) {
if (link->logf)
fprintf(link->logf, "%s: function %s not found\n",
__func__, p_in->name);
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = EOPNOTSUPP;
goto send_reply;
}
pd = flist->pd;
if (link->logf)
fprintf(link->logf, "%s: request for %s\n",
__func__, pd->name);
/* call the function and send back stuff */
i = pd->f(pd, p_in->args, p_out->val);
if (i < 0) {
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = errno;
} else {
/* Use retval, but fix the length for strings */
if (MINIPC_GET_ATYPE(pd->retval) == MINIPC_ATYPE_STRING) {
int size = strlen((char *)p_out->val) + 1;
size = (size + 3) & ~3; /* align */
p_out->type =
__MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, size);
} else {
p_out->type = pd->retval;
}
}
send_reply:
if (shm) {
shm->nreply++; /* message already in place */
return;
}
/* send a 32-bit value plus the declared return length */
if (send(fd, p_out, sizeof(p_out->type)
+ MINIPC_GET_ASIZE(p_out->type), MSG_NOSIGNAL) < 0)
goto close_client;
return;
close_client:
if (link->logf)
fprintf(link->logf, "%s: error %i in fd %i, closing\n",
__func__, i < 0 ? errno : 0, fd);
close(fd);
FD_CLR(fd, &link->fdset);
link->fd[pos] = -1;
return;
}
static void mpc_handle_connection(struct mpc_link *link, int fd)
{
int i, newfd;
struct sockaddr_un sun;
socklen_t slen = sizeof(sun);
newfd = accept(fd, (struct sockaddr *)&sun, &slen);
if (link->logf)
fprintf(link->logf, "%s: accept returned fd %i (error %i)\n",
__func__, newfd, newfd < 0 ? errno : 0);
if (newfd < 0)
return;
/* Lookf for a place for this */
for (i = 0; i < MINIPC_MAX_CLIENTS; i++)
if (link->fd[i] < 0)
break;
if (i == MINIPC_MAX_CLIENTS) {
if (link->logf)
fprintf(link->logf, "%s: refused: too many clients\n",
__func__);
close(newfd);
return;
}
link->fd[i] = newfd;
FD_SET(newfd, &link->fdset);
}
/*
* The server action returns an error or zero. If the user wants
* the list of active descriptors, it must ask for the filemask
* (at this point there is no support for poll, only select)
*/
int minipc_server_action(struct minipc_ch *ch, int timeoutms)
{
struct mpc_link *link = mpc_get_link(ch);
struct timeval to;
fd_set set;
int i;
CHECK_LINK(link);
if ( !link->memaddr ) { // socket mode
to.tv_sec = timeoutms/1000;
to.tv_usec = (timeoutms % 1000) * 1000;
set = link->fdset;
i = select(64 /* FIXME: hard constant */, &set, NULL, NULL, &to);
if (!i)
return 0;
if (i < 0 && errno == EINTR)
return 0;
if (i < 0)
return -1;
if (i == 0) {
errno = EAGAIN;
return -1;
}
} else {
mpc_handle_client(ch, -1, 0);
}
/* First, look for all clients */
for (i = 0; i < MINIPC_MAX_CLIENTS; i++) {
if (link->fd[i] < 0)
continue;
if (!FD_ISSET(link->fd[i], &set))
continue;
mpc_handle_client(ch, i, link->fd[i]);
}
/* Finally, look for a new client */
if (FD_ISSET(ch->fd, &set))
mpc_handle_connection(link, ch->fd);
return 0;
}
#define AVERAGE_REQ_TIME_US 1000 /* 1ms busy wait per request */
/* get monotonic number of useconds */
static uint64_t get_monotonic_us(void)
{
struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv);
return (uint64_t) tv.tv_sec * 1000000ULL +
(uint64_t) (tv.tv_nsec / 1000);
}
int mpc_mem_request(struct minipc_ch *ch, int timeout_ms)
{
uint64_t start_tics = get_monotonic_us();
uint64_t timeout_tics = start_tics + 1000ULL * timeout_ms;
uint64_t busy_tics = start_tics + AVERAGE_REQ_TIME_US;
uint32_t prev;
volatile uint32_t *vptr;
struct mpc_link *link = mpc_get_link(ch);
volatile struct mpc_shmem *shm = link->memaddr;
if (link->flags & MPC_FLAG_SERVER)
{
vptr = &shm->nrequest;
prev = link->last_request_id;
}
else
{
vptr = &shm->nreply;
prev = *vptr;
shm->nrequest++;
}
for(;;)
{
uint64_t t = get_monotonic_us();
if( *vptr != prev )
return 0;
if( t > timeout_tics && timeout_ms > 0 ) {
errno = ETIMEDOUT;
return -1;
} else if ( t > busy_tics ) {
usleep( MINIPC_DEFAULT_POLL );
}
}
return 0;
}
\ No newline at end of file
/*
* Public definition for mini-ipc
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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.
*/
#ifndef __MINIPC_H__
#define __MINIPC_H__
#include <stdint.h>
#if __STDC_HOSTED__ /* freestanding servers have less material */
#include <stdio.h>
#include <sys/select.h>
#endif
/* Hard limits */
#define MINIPC_MAX_NAME 20 /* includes trailing 0 */
#define MINIPC_MAX_CLIENTS 64
#define MINIPC_MAX_ARGUMENTS 256 /* Also, max size of packet words -- 1k */
#define MINIPC_MAX_REPLY 1024 /* bytes */
#if !__STDC_HOSTED__
#define MINIPC_MAX_EXPORT 12 /* freestanding: static allocation */
#endif
/* The base pathname, mkdir is performed as needed */
#define MINIPC_BASE_PATH "/tmp/.minipc"
/* Default polling interval for memory-based channels */
#define MINIPC_DEFAULT_POLL (10*1000) /* us */
/* Argument type (and retval type). The size is encoded in the same word */
enum minipc_at {
MINIPC_ATYPE_ERROR = 0xffff,
MINIPC_ATYPE_NONE = 0, /* used as terminator */
MINIPC_ATYPE_INT = 1,
MINIPC_ATYPE_INT64,
MINIPC_ATYPE_DOUBLE, /* float is promoted to double */
MINIPC_ATYPE_STRING, /* size of strings is strlen() each time */
MINIPC_ATYPE_STRUCT
};
/* Encoding of argument type and size in one word */
#define __MINIPC_ARG_ENCODE(atype, asize) (((atype) << 16) | (asize))
#define MINIPC_ARG_ENCODE(atype, type) __MINIPC_ARG_ENCODE(atype, sizeof(type))
#define MINIPC_GET_ATYPE(word) ((word) >> 16)
#define MINIPC_GET_ASIZE(word) ((word) & 0xffff)
#define MINIPC_ARG_END __MINIPC_ARG_ENCODE(MINIPC_ATYPE_NONE, 0) /* zero */
/* The exported procedure looks like this */
struct minipc_pd;
typedef int (minipc_f)(const struct minipc_pd *, uint32_t *args, void *retval);
/* This is the "procedure definition" */
struct minipc_pd {
minipc_f *f; /* pointer to the function */
char name[MINIPC_MAX_NAME]; /* name of the function */
uint32_t flags;
uint32_t retval; /* type of return value */
uint32_t args[]; /* zero-terminated */
};
/* Flags: verbosity is about argument and retval marshall/unmarshall */
#define MINIPC_FLAG_VERBOSE 1
/* This is the channel definition */
struct minipc_ch {
int fd;
};
static inline int minipc_fileno(struct minipc_ch *ch) {return ch->fd;}
/* These return NULL with errno on error, name is the socket pathname */
struct minipc_ch *minipc_server_create(const char *name, int flags);
struct minipc_ch *minipc_client_create(const char *name, int flags);
int minipc_close(struct minipc_ch *ch);
/* Generic: set the default polling interval for mem-based channels */
int minipc_set_poll(int usec);
/* Server: register exported functions */
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd);
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd);
/* Server: helpers to unmarshall a string or struct from a request */
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype);
/* Handle a request if pending, otherwise -1 and EAGAIN */
int minipc_server_action(struct minipc_ch *ch, int timeoutms);
#if __STDC_HOSTED__
/* Generic: attach diagnostics to a log file */
int minipc_set_logfile(struct minipc_ch *ch, FILE *logf);
/* Return an fdset for the user to select() on the service */
int minipc_server_get_fdset(struct minipc_ch *ch, fd_set *setptr);
/* Client: make requests */
int minipc_call(struct minipc_ch *ch, int millisec_timeout,
const struct minipc_pd *pd, void *ret, ...);
#endif /* __STDC_HOSTED__ */
#endif /* __MINIPC_H__ */
Subproject commit ec89448c1db94019a719c68329297f90edd210de
Subproject commit ca2dd2135ec627b164ff6bfc5dd33b5a22c01be3
......@@ -377,6 +377,7 @@ declare -A inst_dotc_ppsi_key_mapping='(\
[PROFILE_PTP]="profile ptp" [PROFILE_WR]="profile wr" [PROFILE_HA]="profile ha" [PROFILE_CUSTOM]="profile custom" \
[DESIRADE_STATE_MASTER]="desiredState master" [DESIRADE_STATE_SLAVE]="desiredState slave" [DESIRADE_STATE_PASSIVE]="desiredState passive" \
[ANNOUNCE_INTERVAL]="logAnnounceInterval" [ANNOUNCE_RECEIPT_TIMEOUT]="announceReceiptTimeout" \
[SYNC_INTERVAL]="logSyncInterval" \
[MIN_DELAY_REQ_INTERVAL]="logMinDelayReqInterval" [MIN_PDELAY_REQ_INTERVAL]="logMinPDelayReqInterval" \
[ASYMMETRY_CORRECTION_ENABLE]="asymmetryCorrectionEnable" \
[BMODE_MASTER_ONLY]="masterOnly" \
......@@ -426,14 +427,6 @@ if [ -n "$CONFIG_PTP_OPT_DOMAIN_NUMBER" ]; then
globals[domain-number]="$CONFIG_PTP_OPT_DOMAIN_NUMBER"
fi
if [ -n "$CONFIG_PTP_OPT_ANNOUNCE_INTERVAL" ]; then
globals[announce-interval]="$CONFIG_PTP_OPT_ANNOUNCE_INTERVAL"
fi
if [ -n "$CONFIG_PTP_OPT_SYNC_INTERVAL" ]; then
globals[sync-interval]="$CONFIG_PTP_OPT_SYNC_INTERVAL"
fi
if [ -n "$CONFIG_PTP_OPT_PRIORITY1" ]; then
globals[priority1]="$CONFIG_PTP_OPT_PRIORITY1"
fi
......
-include ../Makefile.specific
# if BRVER not defined use a wildcard
BRVER?=2*
SNMP_BUILD := $(wildcard $(WRS_OUTPUT_DIR)/build/buildroot-$(BRVER))
......@@ -21,7 +23,7 @@ OBJDUMP = $(CROSS_COMPILE)objdump
# defer running "net-snmp-config --cflags" so it is visible in make output
CFLAGS += -fPIC -Wall $$($(NET_SNMP_CONFIG) --cflags | sed s,-I/usr/include,,)
LDFLAGS = -shared $$($(NET_SNMP_CONFIG) --ldflags)
CFLAGS += -I../include -I../libwr/include -I../ppsi/include -I../ppsi/arch-wrs/include
CFLAGS += -I../include -I../libwr/include -I../ppsi/include -I../ppsi/arch-wrs/include -I.
CFLAGS += -DWRS_WITH_SNMP_HACKISH_LOG=0
SHLIB = wrsSnmp.so
......
......@@ -98,8 +98,8 @@ time_t wrsPortStatusTable_data_fill(unsigned int *n_rows)
* values defined as WRS_PORT_STATUS_LINK_*
*/
wrsPortStatusTable_array[i].wrsPortStatusLink =
1 + state_up(port_state->state);
if (port_state->state == HAL_PORT_STATE_DISABLED) {
1 + state_up(&port_state);
if (get_port_state(port_state) == HAL_PORT_STATE_DISABLED) {
wrsPortStatusTable_array[i].wrsPortStatusSfpError =
WRS_PORT_STATUS_SFP_ERROR_PORT_DOWN;
/* if port is disabled don't fill
......
-include ../Makefile.specific
# We are now Kconfig-based
-include ../../.config
......
......@@ -666,7 +666,7 @@ void show_ports(int hal_alive, int ppsi_alive)
if (mode == SHOW_GUI) {
/* check if link is up */
if (state_up(port_state->state))
if (state_up(port_state))
term_cprintf(C_GREEN, " %-5s", if_name);
else
term_cprintf(C_RED, "*%-5s", if_name);
......@@ -816,7 +816,7 @@ void show_ports(int hal_alive, int ppsi_alive)
}
term_cprintf(C_WHITE, "\n");
} else if (mode & WEB_INTERFACE) {
printf("%s ", state_up(port_state->state)
printf("%s ", state_up(port_state)
? "up" : "down");
printf("%s ", port_state->locked
? "Locked" : "NoLock");
......@@ -825,7 +825,7 @@ void show_ports(int hal_alive, int ppsi_alive)
? "Calibrated" : "Uncalibrated");
} else if (print_port) {
printf("port:%s ", if_name);
printf("lnk:%d ", state_up(port_state->state));
printf("lnk:%d ", state_up(port_state));
printf("lock:%d ", port_state->locked);
print_port = 0;
}
......
......@@ -20,7 +20,15 @@
#include <sys/mman.h>
#include <signal.h>
#define __EXPORTED_HEADERS__ /* prevent a #warning notice from linux/types.h */
//#define _LINUX_IF_ETHER_H /* prevent linux/if_ether.h that redefines ethhdr */
#include <linux/mii.h>
#include <libwr/shw_io.h>
#include "../libwr/i2c_sfp.h"
#include <regs/endpoint-regs.h>
#include <regs/endpoint-mdio.h>
#undef PACKED
#include <libwr/ptpd_netif.h>
......@@ -473,6 +481,788 @@ char * getRtsTimingMode(int mode) {
return m->name;
}
#define TXCAL_ST_STARTUP 0
#define TXCAL_ST_WAIT_SPLL 1
#define TXCAL_ST_WAIT_PTRACKER 2
#define TXCAL_ST_READY 3
struct port_state
{
int ep;
int state;
int tx_phase_bias;
int tx_phase;
struct bitslide_bin
{
int samples;
int acc;
int rx_phase;
} bins[20];
};
struct key_value
{
char *comment;
char *key;
char *value;
struct key_value *next;
};
struct config_file
{
struct key_value *head;
};
struct key_value *cfg_find_key(struct config_file *cfg, const char *key, int create)
{
struct key_value *kv = cfg->head, *kv_prev = cfg->head;
while (kv)
{
if (kv->key && !strcasecmp(key, kv->key))
{
printf("found key %s %p\n", key, kv);
return kv;
}
kv_prev = kv;
kv = kv->next;
}
if (!create)
return NULL;
if (!cfg->head)
{
cfg->head = malloc(sizeof(struct key_value));
kv = cfg->head;
}
else
{
kv_prev->next = malloc(sizeof(struct key_value));
kv = kv_prev->next;
}
printf("create %s %p\n", key, kv);
kv->key = strdup(key);
kv->value = NULL;
kv->comment = NULL;
kv->next = NULL;
return kv;
}
int cfg_get_int(struct config_file *cfg, const char *key, int *value)
{
struct key_value *kv = cfg_find_key(cfg, key, 0);
if (!kv)
return 0;
*value = atoi(kv->value);
return 1;
}
struct config_file *cfg_open(const char *filename, int overwrite)
{
FILE *f = fopen(filename, "rb");
if (!f)
{
if (overwrite)
{
struct config_file *cfg = malloc(sizeof(struct config_file));
f = fopen(filename, "wb");
cfg->head = NULL;
return cfg;
}
return NULL;
}
struct config_file *cfg = malloc(sizeof(struct config_file));
cfg->head = NULL;
while (!feof(f))
{
char str[1024];
int rv = (int) fgets(str, 1024, f);
int i, pos = -1;
if (rv == 0)
break;
for (i = 0; i < strlen(str); i++)
{
if (str[i] == '=' && pos < 0)
pos = i;
if (str[i] == '\n' || str[i] == '\r')
str[i] = 0;
}
struct key_value *kv = malloc(sizeof(struct key_value));
if (pos < 0 || strlen(str) == 0 || str[0] == '#')
{
kv->comment = strdup(str);
kv->key = NULL;
kv->value = NULL;
}
else
{
kv->comment = NULL;
kv->key = strndup(str, pos);
kv->value = strdup(str + pos + 1);
kv->next = NULL;
printf("KV: %s = %s\n", kv->key, kv->value);
}
if (!cfg->head)
{
cfg->head = kv;
}
else
{
struct key_value *last = cfg->head;
while (last->next)
last = last->next;
last->next = kv;
}
}
return cfg;
}
int cfg_set_int(struct config_file *cfg, const char *key, int value)
{
struct key_value *kv = cfg_find_key(cfg, key, 1);
if (!kv)
return 0;
char buf[1024];
snprintf(buf, 1024, "%d", value);
kv->value = strdup(buf);
return 1;
}
int cfg_save(struct config_file *cfg, const char *filename)
{
FILE *f = fopen(filename, "wb");
if (!f)
{
printf("Can't open '%s' for writing.\n", filename);
return -1;
}
struct key_value *kv = cfg->head;
while (kv)
{
if (kv->comment)
fprintf(f, "%s\n", kv->comment);
else
fprintf(f, "%s=%s\n", kv->key, kv->value);
kv = kv->next;
}
fclose(f);
return 0;
}
int calibrate_tx_delay_fsm(struct port_state *state)
{
struct rts_pll_state pstate;
switch (state->state)
{
case TXCAL_ST_STARTUP:
{
printf("Port %d: start TX calibration\n", state->ep);
pcs_write(state->ep, 0, MDIO_MCR_PDOWN);
pcs_write(state->ep, 0, MDIO_MCR_LOOPBACK); // re-set the PHY and configure in loopback mode
pcs_write(state->ep, 19, 1); // configure DDMTD RX CLK source to TXOUTCLK (TX PMA clock) for calibration
rts_enable_ptracker(state->ep, 0);
rts_get_state(&pstate);
if (pstate.mode != RTS_MODE_GM_FREERUNNING)
{
printf("Setup SPLL to free-running mode\n");
rts_set_mode(RTS_MODE_GM_FREERUNNING);
}
state->state = TXCAL_ST_WAIT_SPLL;
break;
}
case TXCAL_ST_WAIT_SPLL:
{
rts_get_state(&pstate);
if (pstate.flags & RTS_DMTD_LOCKED)
{
rts_enable_ptracker(state->ep, 1);
state->state = TXCAL_ST_WAIT_PTRACKER;
}
break;
}
case TXCAL_ST_WAIT_PTRACKER:
{
rts_get_state(&pstate);
if (pstate.channels[state->ep].flags & CHAN_PMEAS_READY)
{
state->tx_phase = pstate.channels[state->ep].phase_loopback;
printf("Port %d TX Phase = %d ps\n", state->ep, state->tx_phase);
state->state = TXCAL_ST_READY;
}
break;
}
}
return (state->state == TXCAL_ST_READY);
}
#define RXCAL_ST_STARTUP 0
#define RXCAL_ST_RESET_PMA 1
#define RXCAL_ST_WAIT_LINK 2
#define RXCAL_ST_WAIT_TX_PHASE 3
#define RXCAL_ST_WAIT_RX_PHASE 4
#define RXCAL_ST_DONE 5
#define RXCAL_MIN_SAMPLES 2
int calibrate_rx_delay_fsm(struct port_state *state, int use_external_loopback)
{
struct rts_pll_state pstate;
int i;
int min_samples_per_bin = 1000000;
int empty_bins = 0, full_bins = 0;
switch (state->state)
{
case RXCAL_ST_STARTUP:
{
printf("Port %d: start RX calibration\n", state->ep);
state->state = RXCAL_ST_RESET_PMA;
break;
}
case RXCAL_ST_RESET_PMA:
{
pcs_write(state->ep, 0, MDIO_MCR_PDOWN);
pcs_write(state->ep, 0, (use_external_loopback == state->ep) ? 0 : MDIO_MCR_LOOPBACK); // re-set the PHY and configure in loopback mode
pcs_write(state->ep, 19, 1); // configure DDMTD RX CLK source to TXOUTCLK (TX PMA clock) for calibration
rts_enable_ptracker(state->ep, 0);
state->state = RXCAL_ST_WAIT_LINK;
break;
}
case RXCAL_ST_WAIT_LINK:
{
if (pcs_read(state->ep, 1) & MDIO_MSR_LSTATUS)
{
int bitslide = get_bitslide(state->ep);
if (state->bins[bitslide].samples > RXCAL_MIN_SAMPLES)
{
// not interesting, we've got enough phase samples in this bin
state->state = RXCAL_ST_RESET_PMA;
}
else
{
state->state = RXCAL_ST_WAIT_TX_PHASE;
rts_enable_ptracker(state->ep, 1);
}
}
break;
}
case RXCAL_ST_WAIT_TX_PHASE:
{
rts_get_state(&pstate);
if (pstate.channels[state->ep].flags & CHAN_PMEAS_READY)
{
state->tx_phase = pstate.channels[state->ep].phase_loopback;
rts_enable_ptracker(state->ep, 0);
pcs_write(state->ep, 19, 0); // configure DDMTD RX CLK source to RXRECCLK (RX recovered PMA clock)
rts_enable_ptracker(state->ep, 1);
state->state = RXCAL_ST_WAIT_RX_PHASE;
}
break;
}
case RXCAL_ST_WAIT_RX_PHASE:
{
rts_get_state(&pstate);
if (pstate.channels[state->ep].flags & CHAN_PMEAS_READY)
{
int rx_phase = (pstate.channels[state->ep].phase_loopback - (state->tx_phase - state->tx_phase_bias));
int bitslide = get_bitslide(state->ep);
while (rx_phase < 0)
rx_phase += 800;
while (rx_phase > 800)
rx_phase -= 800;
printf("RXph %d TXPh %d bs %d [", state->tx_phase, rx_phase, bitslide);
state->state = RXCAL_ST_RESET_PMA;
if (state->bins[bitslide].samples <= RXCAL_MIN_SAMPLES)
{
state->bins[bitslide].acc += rx_phase;
state->bins[bitslide].samples++;
}
for (i = 0; i < 20; i++)
{
printf("%d ", state->bins[i].samples);
if (state->bins[i].samples < min_samples_per_bin)
min_samples_per_bin = state->bins[i].samples;
if (state->bins[i].samples > RXCAL_MIN_SAMPLES)
full_bins++;
if (state->bins[i].samples == 0)
empty_bins++;
}
printf("] min = %d, empty bins = %d \n", min_samples_per_bin, empty_bins);
if (full_bins == 19)
{
int avg = 0;
for (i = 0; i < 20; i++)
{
if (state->bins[i].samples > RXCAL_MIN_SAMPLES)
{
state->bins[i].acc /= state->bins[i].samples;
avg += state->bins[i].acc;
}
}
avg /= full_bins;
printf("Calibration offsets for port %d:\n", state->ep);
for (i = 0; i < 20; i++)
{
if (state->bins[i].samples > RXCAL_MIN_SAMPLES)
{
state->bins[i].acc -= avg;
printf("bitslide %d: %d ps\n", i, state->bins[i].acc);
}
}
state->state = RXCAL_ST_DONE;
return 1;
}
break;
}
case RXCAL_ST_DONE:
return 1;
}
}
return 0;
}
struct port_state ports[8];
void run_rx_calibration(void)
{
int i;
for (i = 0; i < 8; i++)
{
int j;
ports[i].state = RXCAL_ST_STARTUP;
ports[i].tx_phase_bias = ports[i].tx_phase;
for (j = 0; j < 20; j++)
{
ports[i].bins[j].samples = 0;
ports[i].bins[j].acc = 0;
ports[i].bins[j].rx_phase = 0;
}
ports[i].ep = i;
}
int done;
do
{
done = 1;
for (i = 0; i < 8; i++)
{
if (!calibrate_rx_delay_fsm(&ports[i], -1))
done = 0;
}
usleep(10000);
} while (!done);
}
#if 0
void gtx_phase_test(int ep, int argc, char *argv[])
{
int i,j;
if( rts_connect(NULL) < 0)
{
printf("Can't connect to the RT subsys\n");
return;
}
for(i=0;i<8;i++)
{
ports[i].state = TXCAL_ST_STARTUP;
ports[i].ep = i;
}
int done;
do {
done = 1;
for(i=0;i<8;i++)
{
if( !calibrate_tx_delay_fsm(&ports[i] ) )
done = 0;
}
usleep(10000);
} while (!done);
int full_calib_needed = 0;
struct config_file *cfg = cfg_open("/wr/etc/gtx-calibration.conf", 0);
if(!cfg)
{
printf("No GTX calibration file found. Creating one!\n");
cfg = cfg_open("/wr/etc/gtx-calibration.conf", 1);
full_calib_needed = 1;
for(i=0;i<8;i++)
{
char key[1024];
snprintf(key, 1024, "tx_bias_port%d", i);
cfg_set_int(cfg, key, ports[i].tx_phase);
}
}
if (full_calib_needed)
{
run_rx_calibration();
for(i=0;i<8;i++)
{
for(j =0;j<20;j++)
{
char key[1024];
snprintf(key, 1024, "rx_adjust_port%d_bitslide%d_valid", i, j);
cfg_set_int(cfg, key, ports[i].bins[j].samples > RXCAL_MIN_SAMPLES ? 1 : 0);
snprintf(key, 1024, "rx_adjust_port%d_bitslide%d", i, j);
cfg_set_int(cfg, key, ports[i].bins[j].acc );
}
}
}
cfg_save(cfg, "/wr/etc/gtx-calibration.conf");
run_rx_calibration();
}
#endif
int measure_loobpack_phase(int ep)
{
struct rts_pll_state pstate;
rts_enable_ptracker(ep, 0);
rts_enable_ptracker(ep, 1);
rts_get_state(&pstate);
while(! (pstate.channels[ep].flags & CHAN_PMEAS_READY ) )
{
shw_udelay(10);
rts_get_state(&pstate);
}
return pstate.channels[ep].phase_loopback;
}
void phase_test_mode0(int ep)
{
// shw_sfp_set_generic(ep, 0, SFP_LED_WRMODE1);
pcs_write( ep, 19, 0 | 2 | (1<<2));
pcs_write( ep, 0, MDIO_MCR_PDOWN);
shw_udelay(10);
pcs_write( ep, 0, MDIO_MCR_LOOPBACK);
shw_udelay(10);
pcs_write(ep, 0, pcs_read(ep, 0) | MDIO_MCR_RESET);
pcs_write(ep, 16, 0);
shw_udelay(10);
//shw_sfp_set_tx_disable(ep, 0);
//shw_sfp_set_generic(ep, 1, SFP_LED_WRMODE1);
pcs_write(ep, 0, pcs_read(ep, 0) & ~(MDIO_MCR_RESET));
shw_udelay(10000);
pcs_write(ep, 19, 1 << 14); // enable TXOUTCCLK clock source for DDMTD
//printf("DBG1: 0x%x\n", pcs_read(ep, 19));
//printf("DBG0: 0x%x\n", pcs_read(ep, 18));
//printf("BMSR %x %x\n", pcs_read(ep, 1), pcs_read(ep, 0xa));
int tx_phase = measure_loobpack_phase( ep );
//int tx_phase = 0;
pcs_write(ep, 19, 0);
shw_udelay(1000);
int loop_phase = measure_loobpack_phase( ep );
int dbg0_int = pcs_read(ep, 18);
int bitslide = get_bitslide(ep);
//printf("loop phase %d [%d] bs %d\n", loop_phase, loop_phase % 800, bitslide);
pcs_write( ep, 0, 0);
shw_udelay(1000);
int int_loop_phase = measure_loobpack_phase( ep );
bitslide = get_bitslide(ep);
int dbg0_ext = pcs_read(ep, 18);
//printf("BMSR %x %x\n", pcs_read(ep, 1), pcs_read(ep, 0xa));
printf("p %d %d %d %x %x %d\n", tx_phase, loop_phase, int_loop_phase, dbg0_int, dbg0_ext, bitslide);
}
int force_bitslide(int ep, uint16_t expected_pattern, int loopback)
{
uint16_t pattern;
int n = 0;
do {
pcs_write( ep, 19, 0 | 2 | (1<<2));
pcs_write( ep, 0, MDIO_MCR_PDOWN);
shw_udelay(10);
pcs_write( ep, 0, loopback ? MDIO_MCR_LOOPBACK : 0);
shw_udelay(10);
pcs_write(ep, 0, pcs_read(ep, 0) | MDIO_MCR_RESET);
pcs_write(ep, 16, 0);
shw_udelay(10);
pcs_write(ep, 0, pcs_read(ep, 0) & ~(MDIO_MCR_RESET));
shw_udelay(1000);
pattern = pcs_read(ep, 18);
n++;
} while ( pattern != expected_pattern );
return n;
}
void phase_test_mode1(int ep)
{
uint16_t expected_pattern = 0x50bc;
int n=0;
n = force_bitslide( ep, expected_pattern, 1 );
pcs_write(ep, 19, 1 << 14); // enable TXOUTCCLK clock source for DDMTD
int tx_phase = measure_loobpack_phase( ep );
pcs_write(ep, 19, 0);
shw_udelay(1000);
int loop_phase = measure_loobpack_phase( ep );
//pcs_write( ep, 0, 0);
n = force_bitslide( ep, expected_pattern, 0 );
shw_udelay(1000);
int int_loop_phase = measure_loobpack_phase( ep );
printf("p %d %d %d %d\n", tx_phase, loop_phase, int_loop_phase,n);
}
#define PCS_DBG_RESET_TX (1<<0)
#define PCS_DBG_RESET_TX_DONE (1<<0)
#define PCS_DBG_TX_ENABLE (1<<1)
#define PCS_DBG_RX_ENABLE (1<<2)
#define PCS_DBG_RESET_RX (1<<3)
#define PCS_DBG_DMTD_SOURCE_TXOUTCLK (1<<14)
#define PCS_DBG_LINK_UP (1<<1)
#define PCS_DBG_LINK_ALIGNED (1<<2)
#define MDIO_DBG1 19
#define MDIO_DBG0 18
int force_tx_phase(int ep, int center, int tolerance)
{
int n_attempts = 0;
fprintf(stderr, "Forcing TX phase on EP %d...\n ", ep);
for (;;)
{
n_attempts++;
pcs_write(ep, MDIO_DBG1, PCS_DBG_DMTD_SOURCE_TXOUTCLK | PCS_DBG_RESET_TX); // enable TXOUTCCLK clock source for DDMTD
shw_udelay(1000);
pcs_write(ep, MDIO_DBG1, PCS_DBG_DMTD_SOURCE_TXOUTCLK); // enable TXOUTCCLK clock source for DDMTD
shw_udelay(1000);
// printf("PCS reset done : %d", pcs_read(ep, MDIO_DBG0) & PCS_DBG_RESET_TX_DONE ? 1: 0);
int tx_phase = measure_loobpack_phase(ep);
//fprintf(stderr,"txPhase %d\n", tx_phase);
int n = tx_phase - center + 10000;
if (n > 10000 - tolerance && n < 10000 + tolerance)
{
fprintf(stderr, "locked on %d ps after %d attempts.\n", tx_phase, n_attempts);
return 1;
}
}
return 0;
// printf("p %d\n", tx_phase );
}
int phase_test_mode2(int ep)
{
// disable the PHY
pcs_write(ep, MDIO_DBG1, PCS_DBG_RESET_TX); // enable TXOUTCCLK clock source for DDMTD
force_tx_phase(0, 80, 500);
for (;;)
{
pcs_write(ep, MDIO_DBG1, 0);
shw_udelay(1000);
pcs_write(ep, MDIO_DBG1, PCS_DBG_TX_ENABLE);
shw_udelay(1000);
int n_attempts = 0;
for (;;)
{
shw_sfp_set_tx_disable(ep, 0);
n_attempts++;
pcs_write(ep, MDIO_DBG1, PCS_DBG_TX_ENABLE | PCS_DBG_RESET_RX);
shw_udelay(1000);
pcs_write(ep, MDIO_DBG1, PCS_DBG_TX_ENABLE);
shw_udelay(1000);
while (!(pcs_read(ep, MDIO_DBG0) & PCS_DBG_LINK_UP))
{
shw_udelay(1000);
}
//fprintf(stderr,"Link up\n");
if (pcs_read(ep, MDIO_DBG0) & PCS_DBG_LINK_ALIGNED && n_attempts > 10)
break;
}
//fprintf(stderr,"OK\n");
int loopback_phase = measure_loobpack_phase(ep);
fprintf(stderr, "Link aligned after %d attempts, loopback phase = %d ps\n", n_attempts, loopback_phase);
}
return 0;
}
void gtx_phase_test(int ep, int argc, char *argv[])
{
struct rts_pll_state pstate;
if (argc < 3)
{
printf("Mode expected\n");
return;
}
int mode = atoi(argv[3]);
printf("Mode : %d ep %d\n", mode, ep);
// printf("setmode rv %d\n", rts_set_mode(RTS_MODE_GM_FREERUNNING));
if (rts_connect(NULL) < 0)
{
printf("Can't connect to the RT subsys\n");
return;
}
printf("Waiting for PLL to lock in master mode\n");
shw_io_init();
shw_fpga_mmap_init();
shw_sfp_buses_init();
rts_set_mode(RTS_MODE_GM_FREERUNNING);
while (1)
{
rts_get_state(&pstate);
if (pstate.flags & RTS_DMTD_LOCKED)
break;
sleep(1);
}
shw_sfp_set_tx_disable(ep, 0);
if( mode == 0 )
{
for(;;) phase_test_mode0(ep);
}
if( mode == 1 )
{
for(;;) phase_test_mode1(ep);
}
if( mode == 2 )
{
for(;;) phase_test_mode2(ep);
}
return;
}
void rt_command(int ep, int argc, char *argv[])
{
/* ep is 0..17 */
......@@ -575,6 +1365,12 @@ struct {
"",
"PPS adjustment test",
pps_adjustment_test},
{
"gtx_phase",
"[0/1/2]",
"gtx phase test [test modes: 0,1,2]",
gtx_phase_test},
{
"rt",
......
......@@ -383,6 +383,7 @@ void dump_many_fields(void *addr, struct dump_info *info, int ninfo, char *prefi
#define DUMP_STRUCT struct hal_shmem_header
struct dump_info hal_shmem_info [] = {
DUMP_FIELD(int, nports),
DUMP_FIELD(int, shmemState),
DUMP_FIELD(int, hal_mode),
DUMP_FIELD(int, read_sfp_diag),
DUMP_FIELD(sensor_temp, temp.fpga),
......@@ -401,7 +402,8 @@ struct dump_info hal_port_info [] = {
DUMP_FIELD(int, hw_index),
DUMP_FIELD(int, fd),
DUMP_FIELD(int, hw_addr_auto),
DUMP_FIELD(int, state),
DUMP_FIELD(int, fsm.st.state),
DUMP_FIELD(int, pllFsm.st.state),
DUMP_FIELD(int, fiber_index),
DUMP_FIELD(int, locked),
/* these fields are defined as uint32_t but we prefer %i to %x */
......@@ -442,8 +444,50 @@ struct dump_info hal_port_info [] = {
DUMP_FIELD(uint32_t, t2_phase_transition),
DUMP_FIELD(uint32_t, t4_phase_transition),
DUMP_FIELD(uint32_t, ep_base),
DUMP_FIELD(int, sfpPresent),
DUMP_FIELD(int, has_sfp_diag),
DUMP_FIELD(int, monitor),
/* PPSi instance information */
DUMP_FIELD(int, portMode),
DUMP_FIELD(int, synchronized),
DUMP_FIELD(int, portInfoUpdated),
/* Events to process */
DUMP_FIELD(int, evt_reset),
DUMP_FIELD(int, evt_lock),
DUMP_FIELD(int, evt_linkUp),
};
/* map for fields of hal_port_state.lpdc (hal_shmem.h) */
#undef DUMP_STRUCT
#define DUMP_STRUCT halPortLPDC_t
struct dump_info hal_port_info_lpdc [] = {
DUMP_FIELD(int, isSupported),
DUMP_FIELD(int, txSetupFSM.st.state),
DUMP_FIELD(int, rxSetupFSM.st.state),
};
/* map for fields of hal_port_state.lpdc.txsetup (hal_shmem.h) */
#undef DUMP_STRUCT
#define DUMP_STRUCT halPortLpdcTx_t
struct dump_info hal_port_info_lpdc_txsetup [] = {
DUMP_FIELD(int, attempts),
DUMP_FIELD(int, cal_saved_phase),
DUMP_FIELD(int, cal_saved_phase_valid),
DUMP_FIELD(int, measured_phase),
DUMP_FIELD(int, expected_phase),
DUMP_FIELD(int, tolerance),
DUMP_FIELD(int, update_cnt),
DUMP_FIELD(int, expected_phase_valid),
};
/* map for fields of hal_port_state.lpdc.rxsetup (hal_shmem.h) */
#undef DUMP_STRUCT
#define DUMP_STRUCT halPortLpdcRx_t
struct dump_info hal_port_info_lpdc_rxsetup [] = {
DUMP_FIELD(int, attempts),
};
int dump_hal_mem(struct wrs_shm_head *head)
......@@ -472,9 +516,31 @@ int dump_hal_mem(struct wrs_shm_head *head)
for (i = 0; i < n; i++, p++) {
char prefix[64];
char prefix2[64];
sprintf(prefix,"HAL.port.%d",i+1);
dump_many_fields(p, hal_port_info, ARRAY_SIZE(hal_port_info),prefix);
strcat(prefix,".lpdc");
dump_many_fields(&p->lpdc, hal_port_info_lpdc, ARRAY_SIZE(hal_port_info_lpdc),prefix);
if ( p->lpdc.txSetup) {
halPortLpdcTx_t *txsetup;
if ( (txsetup=wrs_shm_follow(head, p->lpdc.txSetup))!=NULL ) {
strcpy(prefix2,prefix);
strcat(prefix2,".txsetup");
dump_many_fields(txsetup, hal_port_info_lpdc_txsetup, ARRAY_SIZE(hal_port_info_lpdc_txsetup),prefix2);
}
}
if ( p->lpdc.rxSetup) {
halPortLpdcTx_t *rxsetup;
if ( (rxsetup=wrs_shm_follow(head, p->lpdc.rxSetup))!=NULL ) {
strcpy(prefix2,prefix);
strcat(prefix2,".rxsetup");
dump_many_fields(rxsetup, hal_port_info_lpdc_rxsetup, ARRAY_SIZE(hal_port_info_lpdc_rxsetup),prefix2);
}
}
}
return 0;
}
......
-include ../Makefile.specific
# We are now Kconfig-based
-include ../../.config
OBJS = hal_exports.o hal_main.o hal_ports.o hal_timing.o
OBJS = hal_exports.o hal_main.o hal_ports.o hal_timing.o \
hal_port_fsm.o hal_port_fsm_tx.o hal_port_fsm_rx.o hal_port_fsm_pll.o\
hal_port_leds.o hal_timer.o
BINARY = wrsw_hal
......
......@@ -9,3 +9,33 @@
#define NIC_WRITE_PHY_CMD(addr, value) ((((addr) & 0xff) << 16) \
| (1 << 31) \
| ((value) & 0xffff))
/*
* MDIO registers used in Low Phase Drift Calibration
* See kernel/wbgen-regs/endpoint-mdio.h
*/
// address of status and control registers
#define MDIO_LPC_STAT 18
#define MDIO_LPC_CTRL 19
// flags for status and control registers
#define MDIO_LPC_STAT_RESET_TX_DONE (1 << 0)
#define MDIO_LPC_STAT_LINK_UP (1 << 1)
#define MDIO_LPC_STAT_LINK_ALIGNED (1 << 2)
#define MDIO_LPC_STAT_RESET_RX_DONE (1 << 3)
#define MDIO_LPC_CTRL_RESET_TX (1 << 0)
#define MDIO_LPC_CTRL_TX_ENABLE (1 << 1)
#define MDIO_LPC_CTRL_RX_ENABLE (1 << 2)
#define MDIO_LPC_CTRL_RESET_RX (1 << 3)
#define MDIO_LPC_CTRL_DMTD_SOURCE_TXOUTCLK (1 << 14)
#define MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK (0 << 14)
/*
* Address and mask to discover support for Low Phase Drift
* Calibration, taken from endpoint-regs.h
*/
#define EP_ECR_FEAT_LPC (1 << 28)
#define EP_ECR_ADDR 0x0
......@@ -6,16 +6,16 @@
#include <signal.h>
#include <string.h>
#include <libwr/wrs-msg.h>
#include <libwr/pps_gen.h> /* for direct access to DMPLL and PPS generator */
#include "wrsw_hal.h"
#include <rt_ipc.h>
#include <minipc.h>
#include <libwr/wrs-msg.h>
#include <libwr/pps_gen.h> /* for direct access to DMPLL and PPS generator */
#include <libwr/shmem.h>
#include <hal_exports.h> /* for exported structs/function protos */
#include "hal_exports.h" /* for exported structs/function protos */
#include "hal_ports.h"
#include "hal_timing.h"
static struct minipc_ch *hal_ch;
static struct hal_port_state *ports;
......@@ -55,11 +55,11 @@ int halexp_lock_cmd(const char *port_name, int command, int priority)
has already locked to and stabilized the reference
frequency */
case HEXP_LOCK_CMD_CHECK:
rval = hal_port_check_lock(port_name);
rval = hal_port_check_lock_by_name(port_name);
if (rval > 0)
return HEXP_LOCK_STATUS_LOCKED;
else if (!rval)
else if ( rval==0 )
return HEXP_LOCK_STATUS_BUSY;
else
return HEXP_LOCK_STATUS_NONE;
......@@ -77,7 +77,7 @@ int halexp_lock_cmd(const char *port_name, int command, int priority)
* both the PLLs and the PPS Generator. */
int halexp_pps_cmd(int cmd, hexp_pps_params_t * params)
{
int busy,ret;
int ret;
pr_debug("halexp_pps_cmd: cmd=%d\n", cmd);
switch (cmd) {
......@@ -110,7 +110,7 @@ int halexp_pps_cmd(int cmd, hexp_pps_params_t * params)
accommodate the different phase shifts on each port
(and the fiber/cable connected to it).
*/
return rts_adjust_phase(0, params->adjust_phase_shift) ? 0 : -1;
return rts_adjust_phase(0, params->adjust_phase_shift);
case HEXP_PPSG_CMD_ADJUST_NSEC:
shw_pps_gen_adjust(PPSG_ADJUST_NSEC, params->adjust_nsec);
......@@ -142,12 +142,10 @@ int halexp_pps_cmd(int cmd, hexp_pps_params_t * params)
return shw_pps_gen_enable_output(params->pps_valid);
case HEXP_PPSG_CMD_SET_TIMING_MODE:
ret=shw_pps_set_timing_mode(params->timing_mode);
hal_update_timing_mode();
return ret;
return hal_tmg_set_mode(params->timing_mode);
case HEXP_PPSG_CMD_GET_TIMING_MODE:{
ret=hal_get_timing_mode();
ret=hal_tmg_get_mode(NULL);
return ret;
}
......@@ -161,13 +159,13 @@ int halexp_pps_cmd(int cmd, hexp_pps_params_t * params)
int halexp_port_info_cmd(hexp_port_info_params_t * params)
{
int i;
/* TODO: Improve/implement handling of many PPSi instances on a
single physical port (see more info in hal_port_update_info() */
for ( i=0; i< params->numberPortInterfaces; i++ )
hal_update_port_info(params->hIFace[i].name, params->hIFace[i].mode, params->hIFace[i].synchronized);
hal_port_update_info(params->hIFace[i].name, params->hIFace[i].mode, params->hIFace[i].synchronized);
return 1;
}
extern int hal_port_any_locked(void);
static void hal_cleanup_wripc(void)
{
minipc_close(hal_ch);
......@@ -212,7 +210,7 @@ static int export_port_info_cmd(const struct minipc_pd *pd,
/* Creates a wripc server and exports all public API functions */
int hal_init_wripc(struct hal_port_state *hal_ports, char *logfilename)
int hal_wripc_init(struct hal_port_state *hal_ports, char *logfilename)
{
static FILE *f;
......@@ -254,7 +252,7 @@ int hal_init_wripc(struct hal_port_state *hal_ports, char *logfilename)
}
/* wripc update function, must be called in the main program loop */
int hal_update_wripc(int ms_timeout)
int hal_wripc_update(int ms_timeout)
{
minipc_server_action(hal_ch, ms_timeout);
return 0;
......
......@@ -11,6 +11,8 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <rt_ipc.h>
#include <libwr/wrs-msg.h>
#include <libwr/switch_hw.h>
#include <libwr/shw_io.h>
......@@ -21,12 +23,39 @@
#include <libwr/util.h>
#include <libwr/timeout.h>
#include "wrsw_hal.h"
#include <rt_ipc.h>
#include "hal_ports.h"
#include "hal_timer.h"
#include "hal_timing.h"
#define MAX_CLEANUP_CALLBACKS 16
#define UPDATE_FAN_PERIOD 500
#define UPDATE_ALL_PERIOD 100
typedef enum {
TMO_UPDATE_ALL=0,
TMO_UPDATE_FAN,
TMO_COUNT
}main_tmo_id_t;
static void cb_timer_update_fan(int timerId);
static void cb_timer_update_all(int timerId);
/* Polling timeouts (RT Subsystem & SFP detection) */
static timer_parameter_t _timerParameters[] = {
{
.id=TMO_UPDATE_ALL,
.tmoMs=100, // 100ms
.repeat=1,
.cb=cb_timer_update_all
},
{
.id=TMO_UPDATE_FAN,
.tmoMs=500, // 500ms
.repeat=1,
.cb=cb_timer_update_fan
},
};
#define MAIN_TIMER_COUNT (sizeof(_timerParameters)/sizeof(timer_parameter_t))
static int daemon_mode = 0;
static hal_cleanup_callback_t cleanup_cb[MAX_CLEANUP_CALLBACKS];
......@@ -35,6 +64,7 @@ static char *dotconfigname = "/wr/etc/dot-config";
struct hal_shmem_header *hal_shmem;
struct wrs_shm_head *hal_shmem_hdr;
struct hal_temp_sensors temp_sensors;
/* Adds a function to be called during the HAL shutdown. */
int hal_add_cleanup_callback(hal_cleanup_callback_t cb)
......@@ -114,16 +144,14 @@ static int hal_init(void)
/* Low-level hw init, init non-kernel drivers */
assert_init(shw_init());
/* read timing mode from dot-config */
assert_init(hal_init_timing_mode());
/* Init timing part */
assert_init(hal_tmg_init(logfilename));
/* Initialize HAL's shmem - see hal_ports.c */
assert_init(hal_port_init_shmem(logfilename));
assert_init(hal_init_timing(logfilename));
assert_init(hal_port_shmem_init(logfilename));
/* Initialize IPC/RPC - see hal_ports.c */
assert_init(hal_port_init_wripc(logfilename));
assert_init(hal_port_wripc_init(logfilename));
//everything is fine up to here, we can blink green LED
shw_io_write(shw_io_led_state_o, 0);
......@@ -223,11 +251,29 @@ static void hal_parse_cmdline(int argc, char *argv[])
}
}
static void cb_timer_update_fan(int timerId) {
/* Update fans and get temperatures values. Don't write
* temperatures directly to the shmem to reduce the
* critical section of shmem */
shw_update_fans(&temp_sensors);
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
memcpy(&hal_shmem->temp, &temp_sensors,
sizeof(temp_sensors));
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
}
static void cb_timer_update_all(int timerId) {
hal_port_update_all();
}
int hal_get_fpga_temperature(void)
{
return temp_sensors.fpga;
}
int main(int argc, char *argv[])
{
struct hal_temp_sensors temp_sensors; /* local copy of temperatures */
static timeout_t update_fan_tmo;
static timeout_t update_all_tmo;
wrs_msg_init(argc, argv, LOG_DAEMON);
......@@ -246,8 +292,7 @@ int main(int argc, char *argv[])
if (hal_init())
exit(1);
libwr_tmo_init(&update_fan_tmo, UPDATE_FAN_PERIOD, 1);
libwr_tmo_init(&update_all_tmo, UPDATE_ALL_PERIOD, 1);
timer_init(_timerParameters,MAIN_TIMER_COUNT);
/*
* Main loop update - polls for WRIPC requests and rolls the port
......@@ -261,20 +306,15 @@ int main(int argc, char *argv[])
*/
for (;;) {
hal_update_wripc(25 /* max ms delay */);
if (libwr_tmo_expired(&update_all_tmo))
hal_port_update_all();
if (libwr_tmo_expired(&update_fan_tmo)) {
/* Update fans and get temperatures values. Don't write
* temperatures directly to the shmem to reduce the
* critical section of shmem */
shw_update_fans(&temp_sensors);
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
memcpy(&hal_shmem->temp, &temp_sensors,
sizeof(temp_sensors));
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
hal_wripc_update(25 /* max ms delay */);
// Check main timers and call callback if timeout expires
timer_scan(_timerParameters,MAIN_TIMER_COUNT);
if ( hal_shmem->shmemState== HAL_SHMEM_STATE_INITITALIZING) {
// Check if all ports have been initialized
if ( hal_port_all_ports_initialized())
hal_shmem->shmemState= HAL_SHMEM_STATE_INITITALIZED;
}
}
......
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_MAIN_H
#define HAL_MAIN_H
extern int hal_get_fpga_temperature(void);
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <linux/if.h>
#include <stdlib.h>
#include <rt_ipc.h>
#include <libwr/hal_shmem.h>
#include <libwr/switch_hw.h>
#include <libwr/wrs-msg.h>
#include <libwr/generic_fsm.h>
#include "driver_stuff.h"
#include "hal_exports.h"
#include "hal_ports.h"
#include "hal_port_leds.h"
#include "hal_port_fsmP.h"
#include "hal_port_fsm_rx.h"
#include "hal_port_fsm_tx.h"
#include "hal_port_fsm_pll.h"
#include "hal_timing.h"
#define __EXPORTED_HEADERS__ /* prevent a #warning notice from linux/types.h */
#include <linux/mii.h>
/**
* State machine
* States :
* - HAL_PORT_STATE_INIT:
* Inital state
* - HAL_PORT_STATE_DISABLED:
* The port is disabled and waiting for a SFP insertion
* - HAL_PORT_STATE_LINK_DOWN:
* A Sfp is inserted but the port is waiting for being up
* - HAL_PORT_STATE_LINK_UP:
* The port is UP and operational
* Events :
* - timer : triggered regularly to execute background work
* - sfpInserted: A Sfp has been inserted
* - sfpRemoved : A Sfp has been removed
* - linkUp : A link up has been detected (driver ioctl)
* - Link down : Port is going down
* - reset : Reset of the port is requested
*
*/
/* external prototypes */
static int port_fsm_build_events(fsm_t *fsm);
static int port_fsm_state_init(fsm_t *fsm, int eventMsk, int isNewState);
static int port_fsm_state_disabled(fsm_t *fsm, int eventMsk, int isNewState);
static int port_fsm_state_link_down(fsm_t *fsm, int eventMsk, int isNewState);
static int port_fsm_state_link_up(fsm_t *fsm, int eventMsk, int isNewState);
static void init_port(struct hal_port_state * ps);
static void reset_port(struct hal_port_state * ps);
static int get_port_link_state(struct hal_port_state * ps,int *linkUp);
static fsm_state_table_entry_t port_fsm_states[] =
{
{ .state=HAL_PORT_STATE_INIT,
.stateName="INIT",
FSM_SET_FCT_NAME(port_fsm_state_init)
},
{ .state=HAL_PORT_STATE_DISABLED,
.stateName="DISABLED",
FSM_SET_FCT_NAME(port_fsm_state_disabled)
},
{ .state=HAL_PORT_STATE_LINK_DOWN,
.stateName="LINK_DOWN",
FSM_SET_FCT_NAME(port_fsm_state_link_down)
},
{ .state=HAL_PORT_STATE_LINK_UP,
.stateName="LINK_UP",
FSM_SET_FCT_NAME(port_fsm_state_link_up)
},
{ .state=-1 }
};
static fsm_event_table_entry_t port_fsm_events[] = {
{
.evtMask = HAL_PORT_EVENT_TIMER,
.evtName="TIM"
},
{
.evtMask =HAL_PORT_EVENT_SFP_PRESENT,
.evtName = "SFP"
},
{
.evtMask =HAL_PORT_EVENT_LINK_UP,
.evtName = "LKUP"
},
{ .evtMask =HAL_PORT_EVENT_RESET,
.evtName = "RST"
},
{ .evtMask =HAL_PORT_EVENT_POWER_DOWN,
.evtName = "PDOWN"
},
{ .evtMask =HAL_PORT_EVENT_EARLY_LINK_UP,
.evtName = "ELKUP"
},
{ .evtMask =HAL_PORT_EVENT_RX_ALIGNED,
.evtName = "RXALGN"
},
{ .evtMask = -1 } };
/* INIT state
*
* if entering in state then
* init port
* init TX SETUP FSM
* fi
* run TX SETUP FSM
* if final state reached (TX SETUP FSM) then state = DISABLED
*
*/
static int port_fsm_state_init(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState ) {
init_port(ps);
/* Init tx state machine */
hal_port_tx_setup_fsm_init(ps);
}
/* if final state reached for tx setup state machine ON ALL PORTS
* then we can go to DISABLED state
*/
if (hal_port_tx_setup_fsm_run(ps)==1 )
fsm_fire_state(fsm,HAL_PORT_STATE_DISABLED);
return 0;
}
/*
* DISABLED state
*
* if entering in state then reset port
* if SFP inserted and not port power-down then the state=LINK_DOWN
*/
static int port_fsm_state_disabled(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState ) {
reset_port(ps);
// make sure the PHY calibration circuitry is put in a KNOWN state
if( ps->lpdc.isSupported ) {
pcs_writel(ps,
MDIO_LPC_CTRL_RESET_RX | MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK
| MDIO_LPC_CTRL_TX_ENABLE, MDIO_LPC_CTRL);
}
// Disable tracker
rts_enable_ptracker(ps->hw_index, 0);
}
if ( _isHalEventSfpPresent(eventMsk) && !_isHalEventPortPowerDown( eventMsk ))
fsm_fire_state(fsm,HAL_PORT_STATE_LINK_DOWN);
return 0;
}
/* LINK_DOWN state
*
* if SFP removed event then state= DISABLED
* if entering in state then
* reset port
* init RX_SETUP FSM
* fi
* run RX_SETUP FSM
* if final state (RX_SETUP FSM ) reached then state=LINK_UP
*/
static int port_fsm_state_link_down(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
// High priority event received
if ( !_isHalEventSfpPresent(eventMsk) ) {
fsm_fire_state(fsm,HAL_PORT_STATE_DISABLED);
return 0;
}
if( _isHalEventPortPowerDown( eventMsk ))
{
pr_info("%s: Port wri%d PDOWN detected\n" ,__func__,ps->hw_index + 1 );
// MII power down
fsm_fire_state(fsm,HAL_PORT_STATE_DISABLED);
return 0;
}
if (isNewState) {
reset_port(ps); // clears ps->tx_cal_pending & ps->calib*, except ps->calib.bitslide_ps
/* Init the rx state machine */
hal_port_rx_setup_fsm_init(ps);
/* Turn off both leds when detecting link down. The WRMODE led
might be overriden later by the rx_setup_state_fsm*/
led_set_wrmode(ps->hw_index, SFP_LED_WRMODE_OFF);
led_set_synched(ps->hw_index, 0);
}
/* if final state reached for tx setup state machine then
* we can go LINK_UP state
*/
if (hal_port_rx_setup_fsm_run(ps) == 1 && _isHalEventLinkUp(eventMsk)) {
/* measure bitslide regardless of LPDC support,
(if not supported, the value of the register will be zero) */
uint32_t bit_slide_steps;
if (pcs_readl(ps, 16, &bit_slide_steps) >= 0) {
bit_slide_steps = (bit_slide_steps >> 4) & 0x1f;
/* FIXME: use proper register names */
ps->calib.bitslide_ps = bit_slide_steps * (uint32_t) 800; /* 1 step = 800ps */
/* any calibration, if any, has been done*/
ps->calib.tx_calibrated = 1;
ps->calib.rx_calibrated = 1;
ps->calib.delta_rx_phy = ps->calib.phy_rx_min;
ps->calib.delta_tx_phy = ps->calib.phy_tx_min;
ps->tx_cal_pending = 0;
ps->rx_cal_pending = 0;
pr_info("%s:%s: bitslide= %u [ps]\n", __func__, ps->name,
ps->calib.bitslide_ps);
fsm_fire_state(fsm, HAL_PORT_STATE_LINK_UP);
} else
pr_warning("Cannot read bitslide, retrying...\n");
}
return 0;
}
/* LINK_UP state :
*
* if SFP removed event then state= DISABLED
* if reset or link down events then state= LINK_DOWN
* if entering in state then inititialize PLL FSM
* Run PLL FSM
* Update Leds
*/
static int port_fsm_state_link_up(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( ps->lpdc.isSupported) {
if ( !_isHalEventPortEarlyLinkUp(eventMsk)) {
fsm_fire_state(fsm,HAL_PORT_STATE_LINK_DOWN);
}
}
if ( !_isHalEventSfpPresent(eventMsk) ) {
fsm_fire_state(fsm,HAL_PORT_STATE_DISABLED);
return 0;
}
if( _isHalEventPortPowerDown( eventMsk ))
{
pr_info("%s: Port wri%d PDOWN detected\n" ,__func__,ps->hw_index + 1 );
// MII power down
fsm_fire_state(fsm,HAL_PORT_STATE_DISABLED);
return 0;
}
if ( _isHalEventReset(eventMsk) || !_isHalEventLinkUp(eventMsk)) {
fsm_fire_state(fsm,HAL_PORT_STATE_LINK_DOWN);
return 0;
}
if ( isNewState ) {
// Init PLL FSM
hal_port_pll_fsm_init(ps);
}
if (isRtsStateValid() ) {
struct channel *ch=&getRtsState().channels[ps->hw_index];
ps->phase_val = ch->phase_loopback;
ps->phase_val_valid =ch->flags & CHAN_PMEAS_READY ? 1 : 0;
pr_debug("wri%d: phase=%d valid=%d\n",ps->hw_index+1,
ps->phase_val,ps->phase_val_valid);
}
// Run PLL state machine
hal_port_pll_fsm_run(ps);
// Update leds
{
// Update synced led
int ledValue= ps->synchronized
&& (ps->portInfoUpdated--) > -50 // 50 * 100ms (FSM call rate) = 5seconds
? 1 : 0;
led_set_synched(ps->hw_index, ledValue);
// Update link led
if (ps->portMode == PORT_MODE_SLAVE)
ledValue=SFP_LED_WRMODE_SLAVE;
else if (ps->portMode == PORT_MODE_MASTER)
ledValue=SFP_LED_WRMODE_MASTER;
else
ledValue=SFP_LED_WRMODE_OTHER;
led_set_wrmode(ps->hw_index,ledValue);
}
return 0;
}
/*
* Build all events
*/
static int port_fsm_build_events(fsm_t *fsm) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
int portEventMask=HAL_PORT_EVENT_TIMER;
if ( ps->evt_linkUp > 0 ) {
portEventMask |= HAL_PORT_EVENT_LINK_UP;
}
if ( ps->evt_reset ) {
portEventMask |= HAL_PORT_EVENT_RESET;
ps->evt_reset=0;
}
if (ps->sfpPresent)
portEventMask |= HAL_PORT_EVENT_SFP_PRESENT;
if ( ps->evt_powerDown )
portEventMask |= HAL_PORT_EVENT_POWER_DOWN;
if ( ps->lpdc.isSupported ) {
uint32_t mioLpcStat;
if ( pcs_readl(ps, MDIO_LPC_STAT,&mioLpcStat) >= 0 ) {
if (mioLpcStat & MDIO_LPC_STAT_LINK_UP)
portEventMask |= HAL_PORT_EVENT_EARLY_LINK_UP;
if (mioLpcStat & MDIO_LPC_STAT_LINK_ALIGNED)
portEventMask |= HAL_PORT_EVENT_RX_ALIGNED;
}
}
return portEventMask;
}
/* Init the FSM on all ports. Called one time at startup */
void hal_port_state_fsm_init_all( struct hal_port_state * ports, halGlobalLPDC_t *globalLpdc)
{
int portIndex;
for (portIndex = 0; portIndex < HAL_MAX_PORTS; portIndex++) {
struct hal_port_state* ps = &ports[portIndex];
if ( ps->in_use)
{
char name[64];
snprintf(name, sizeof(name), "PortFsm.%d", portIndex);
if ( fsm_generic_create( &ps->fsm, name, port_fsm_build_events, port_fsm_states, port_fsm_events, ps ) ){
pr_error("Cannot create main fsm !\n");
exit(EXIT_FAILURE);
}
fsm_fire_state(&ps->fsm , HAL_PORT_STATE_INIT);
}
}
hal_port_tx_setup_init_all(ports, globalLpdc);
hal_port_rx_setup_init_all(ports);
hal_port_pll_setup_init_all(ports);
}
/* Call FSM for on all ports */
void hal_port_state_fsm_run_all( struct hal_port_state * ports) {
int portIndex;
hal_port_poll_rts_state(); // Update rts state on all ports
/* Call state machine for all ports */
for (portIndex = 0; portIndex < HAL_MAX_PORTS; portIndex++) {
struct hal_port_state* ps = &ports[portIndex];
if ( ps->in_use) {
uint32_t bmcr;
pcs_readl( ps, MII_BMCR, &bmcr );
ps->evt_powerDown = (bmcr & BMCR_PDOWN);
if ( get_port_link_state(ps, &ps->evt_linkUp) < 0 ) {
// IOTCL error : We put -1 in the link state. It will be considered as invalid
ps->evt_linkUp=-1;
}
fsm_generic_run(&ps->fsm);
}
}
}
/* Reset port
* Called when entering in states DISABLED and LINK_DOWN and when the port is initialized the first time
*/
static void reset_port(struct hal_port_state * ps)
{
// Disable ptracker : Needed if we were in state LINK_UP with a timing mode set to BC
rts_enable_ptracker(ps->hw_index, 0);
// Clear data
ps->calib.rx_calibrated =
ps->calib.tx_calibrated =
ps->locked = 0;
ps->lock_state =
ps->tx_cal_pending =
ps->rx_cal_pending = 0;
ps->portMode= PORT_MODE_OTHER;
ps->synchronized=ps->portInfoUpdated=0;
ps->locked=0;
ps->calib.phy_tx_min = ps->calib.phy_rx_min = 0; // No longer used
ps->calib.delta_tx_board = 0; /* never set */
ps->calib.delta_rx_board = 0; /* never set */
ps->tx_cal_pending = 0;
ps->rx_cal_pending = 0;
/* Turn off link/wrmode LED. Master/slave
* color is set in the different place */
led_set_wrmode(ps->hw_index,SFP_LED_WRMODE_OFF);
/* turn off synced LED */
led_set_synched(ps->hw_index, 0);
}
/* Port initialization */
static void init_port(struct hal_port_state * ps)
{
reset_port(ps);
ps->t2_phase_transition = DEFAULT_T2_PHASE_TRANS;
ps->t4_phase_transition = DEFAULT_T4_PHASE_TRANS;
ps->clock_period = REF_CLOCK_PERIOD_PS;
}
/* Checks if the link is up on inteface (if_name). Returns non-zero if yes. */
static int get_port_link_state(struct hal_port_state * ps,int *linkUp)
{
struct ifreq ifr;
strncpy(ifr.ifr_name, ps->name, sizeof(ifr.ifr_name));
if (ioctl(halPorts.hal_port_fd, SIOCGIFFLAGS, &ifr) < 0 ) {
pr_error("%s: IOCTL error detected : Cannot check link up on %s",__func__,ps->name);
return -1;
}
*linkUp=(ifr.ifr_flags & IFF_UP && ifr.ifr_flags & IFF_RUNNING);
return 0;
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_H
#define HAL_PORT_FSM_H
#define HAL_CAL_DMTD_SAMPLES 16
#define HAL_DEFAULT_DMTD_SAMPLES 512
void hal_port_state_fsm_init_all( struct hal_port_state * ports, halGlobalLPDC_t *globalLpdc);
void hal_port_state_fsm_run_all( struct hal_port_state * ports);
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSMP_H
#define HAL_PORT_FSMP_H
#include "hal_port_fsm.h"
typedef enum
{
HAL_PORT_EVENT_TIMER = (1 << 0),
HAL_PORT_EVENT_SFP_PRESENT = (1 << 1),
HAL_PORT_EVENT_LINK_UP = (1 << 2),
HAL_PORT_EVENT_RESET = (1 << 3),
HAL_PORT_EVENT_POWER_DOWN = (1 << 4),
HAL_PORT_EVENT_EARLY_LINK_UP = (1 << 5),
HAL_PORT_EVENT_RX_ALIGNED = (1 << 6)
} halPortEventMask_t;
static inline int _isHalEventInitialized(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_TIMER;
}
static inline int _isHalEventSfpPresent(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_SFP_PRESENT;
}
static inline int _isHalEventLinkUp(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_LINK_UP;
}
static inline int _isHalEventReset(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_RESET;
}
static inline int _isHalEventPortPowerDown(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_POWER_DOWN;
}
static inline int _isHalEventPortEarlyLinkUp(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_EARLY_LINK_UP;
}
static inline int _isHalEventPortRxAligned(halPortEventMask_t eventMsk) {
return eventMsk & HAL_PORT_EVENT_RX_ALIGNED;
}
#endif
#include <stdlib.h>
#include <hal_exports.h>
#include <libwr/hal_shmem.h>
#include "hal_ports.h"
#include "hal_timing.h"
#include "hal_port_fsm_pllP.h"
/**
* State machine
* States :
* - HAL_PORT_PLL_STATE_UNLOCKED: Inital state
* - HAL_PORT_PLL_STATE_LOCKING : Waiting PLL locking
* - HAL_PORT_PLL_STATE_LOCKED : Final state. Pll locked
* Events :
* - HAL_PORT_PLL_EVENT_TIMER : Use to do background stuff
* - HAL_PORT_PLL_EVENT_LOCK : Lock is requested (timing mode=BC)
* - HAL_PORT_PLL_EVENT_LOCKED : Port is locked
* - HAL_PORT_PLL_EVENT_UNLOCK : Pll is unlocked
* - HAL_PORT_PLL_EVENT_DISABLED : PLL lock is not requested
*
*
*/
/* external prototypes */
static int port_pll_fsm_build_events(fsm_t *pfg);
static int _hal_port_pll_state_unlocked(fsm_t *pfg, int eventMsk, int isNewState);
static int _hal_port_pll_state_locked(fsm_t *pfg, int eventMsk, int isNewState);
static int _hal_port_pll_state_locking(fsm_t *pfg, int eventMsk, int isNewState);
static fsm_state_table_entry_t port_pll_fsm_states[] =
{
{
.state=HAL_PORT_PLL_STATE_UNLOCKED,
.stateName="UNLOCKED",
FSM_SET_FCT_NAME(_hal_port_pll_state_unlocked)
},
{
.state=HAL_PORT_PLL_STATE_LOCKING,
.stateName="LOCKING",
FSM_SET_FCT_NAME(_hal_port_pll_state_locking)
},
{
.state=HAL_PORT_PLL_STATE_LOCKED,
.stateName="LOCKED",
FSM_SET_FCT_NAME(_hal_port_pll_state_locked)
},
{ .state=-1 }
};
static fsm_event_table_entry_t port_pll_fsm_events[] = {
{
.evtMask = HAL_PORT_PLL_EVENT_TIMER,
.evtName="TIM"
},
{
.evtMask = HAL_PORT_PLL_EVENT_LOCK,
.evtName="LOCK"
},
{
.evtMask = HAL_PORT_PLL_EVENT_UNLOCKED,
.evtName="UNLOCKED"
},
{
.evtMask = HAL_PORT_PLL_EVENT_LOCKED,
.evtName="LOCKED"
},
{
.evtMask = HAL_PORT_PLL_EVENT_DISABLE,
.evtName="DISABLE"
},
{ .evtMask = -1 } };
/* UNLOCKED state
*
* if locked event then
* state=LOCKED (should not happen )
* else
* if lock event then
* lock channel
* state=LOCKING
* fi
* fi
*
*/
static int _hal_port_pll_state_unlocked(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState ) {
ps->locked=0;
}
if ( _isHalPllEventLocked(eventMsk) ) {
fsm_fire_state(fsm, HAL_PORT_PLL_STATE_LOCKED);
} else
if ( _isHalPllEventLock(eventMsk) ) {
fsm_fire_state(fsm, HAL_PORT_PLL_STATE_LOCKING);
}
return 0;
}
/*
* LOCKING state
*
* if locked event then state=LOCKED
* else if unlock event then state=UNLOCKED
*/
static int _hal_port_pll_state_locking(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState ) {
ps->locked=0;
}
if ( _isHalPllEventLocked(eventMsk) ) {
fsm_fire_state(fsm, HAL_PORT_PLL_STATE_LOCKED);
return 0;
}
if ( _isHalPllEventDisable(eventMsk) ) {
fsm_fire_state(fsm, HAL_PORT_PLL_STATE_UNLOCKED);
return 0;
}
return 0;
}
/*
* LOCKED state
*
* if unlock event then
* state = LOCKING
* else if disabled event then state=UNLOCK
* else return final state machine reached
* fi
*/
static int _hal_port_pll_state_locked(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState ) {
ps->locked=1;
}
if ( _isHalPllEventUnlock(eventMsk) ) {
fsm_fire_state(fsm, HAL_PORT_PLL_STATE_LOCKING);
return 0;
}
if ( _isHalPllEventDisable(eventMsk) ) {
fsm_fire_state(fsm, HAL_PORT_PLL_STATE_UNLOCKED);
return 0;
}
return 1; /* final state */
}
/*
* Build FSM events
*
* Lock & Locked events are generated only if the timing mode is BC
*/
static int port_pll_fsm_build_events(fsm_t *fsm) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
int portEventMask=HAL_PORT_PLL_EVENT_TIMER;
int tm;
uint32_t hwIndex;
tm= hal_tmg_get_mode(&hwIndex);
if ( tm == HAL_TIMING_MODE_BC && ps->evt_lock) {
portEventMask |= HAL_PORT_PLL_EVENT_LOCK;
ps->evt_lock=0; // Event consumed
return portEventMask;
}
if ( ps->hw_index==hwIndex && tm == HAL_TIMING_MODE_BC) {
int locked=hal_port_check_lock(ps);
if ( locked >=0 ) {
portEventMask|= locked ?
HAL_PORT_PLL_EVENT_LOCKED : HAL_PORT_PLL_EVENT_UNLOCKED;
}
} else {
portEventMask |= HAL_PORT_PLL_EVENT_DISABLE;
}
return portEventMask;
}
/* Initialize rx_setup - this is a global init, executed once for all ports/FSMs.
*/
void hal_port_pll_setup_init_all(struct hal_port_state * ports) {
char name[64];
int index;
for (index = 0; index < HAL_MAX_PORTS; index++){
struct hal_port_state *ps = &ports[index];
snprintf(name, sizeof(name), "PortPllFSM.%d", ps->hw_index);
if (fsm_generic_create(&ps->pllFsm, name, port_pll_fsm_build_events,
port_pll_fsm_states, port_pll_fsm_events, ps)) {
pr_error("Cannot create PLL fsm !\n");
exit(EXIT_FAILURE);
}
}
}
/* Init PLL FSM */
void hal_port_pll_fsm_init(struct hal_port_state * ps )
{
fsm_init_state(&ps->pllFsm);
fsm_fire_state(&ps->pllFsm, HAL_PORT_PLL_STATE_UNLOCKED);
}
void hal_port_pll_fsm_reset(struct hal_port_state * ps )
{
fsm_init_state(&ps->pllFsm);
fsm_fire_state(&ps->pllFsm, HAL_PORT_PLL_STATE_UNLOCKED);
}
/* FSM state machine for PLL on a given port
* Returned value:
* 1: when final state has been reached
* 0: when final state has not been reached
* -1: error detected
*/
int hal_port_pll_fsm_run( struct hal_port_state * ps ) {
if ( !ps->in_use )
return 1;
return fsm_generic_run( &ps->pllFsm );
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_PLL_H
#define HAL_PORT_FSM_PLL_H
void hal_port_pll_fsm_init(struct hal_port_state * ps );
int hal_port_pll_fsm_run( struct hal_port_state * ps );
void hal_port_pll_setup_init_all(struct hal_port_state * ports);
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_PLLP_H
#define HAL_PORT_FSM_PLLP_H
#include <libwr/wrs-msg.h>
#include <libwr/generic_fsm.h>
#include "hal_port_fsm_pll.h"
typedef enum {
HAL_PORT_PLL_STATE_UNLOCKED=0,
HAL_PORT_PLL_STATE_LOCKING,
HAL_PORT_PLL_STATE_LOCKED
} halPortPllState_t;
typedef enum
{
HAL_PORT_PLL_EVENT_TIMER=(1<<0),
HAL_PORT_PLL_EVENT_UNLOCKED=(1<<1),
HAL_PORT_PLL_EVENT_LOCK=(1<<2),
HAL_PORT_PLL_EVENT_LOCKED=(1<<3),
HAL_PORT_PLL_EVENT_DISABLE=(1<<4),
}halPortPllEventMask_t ;
static inline int _isHalPllEventLock(halPortPllEventMask_t eventMsk) {
return eventMsk & HAL_PORT_PLL_EVENT_LOCK;
}
static inline int _isHalPllEventLocked(halPortPllEventMask_t eventMsk) {
return eventMsk & HAL_PORT_PLL_EVENT_LOCKED;
}
static inline int _isHalPllEventUnlock(halPortPllEventMask_t eventMsk) {
return eventMsk & HAL_PORT_PLL_EVENT_UNLOCKED;
}
static inline int _isHalPllEventDisable(halPortPllEventMask_t eventMsk) {
return eventMsk & HAL_PORT_PLL_EVENT_DISABLE;
}
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#define __EXPORTED_HEADERS__ /* prevent a #warning notice from linux/types.h */
#include <linux/mii.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdint.h>
#include <stdlib.h>
#include <libwr/wrs-msg.h>
#include <libwr/hal_shmem.h>
#include <hal_exports.h>
#include "driver_stuff.h"
#include "hal_ports.h"
#include "hal_port_leds.h"
#include "hal_port_fsm_rxP.h"
#include "hal_port_fsm_txP.h"
/**
* State machine
* States :
* - HAL_PORT_RX_SETUP_STATE_START:
* Inital state
* - .....
* - HAL_PORT_RX_SETUP_STATE_DONE:
* RX setup terminated
* Events :
* - timer : triggered regularly to execute background work
* - linkUp : A link up has been detected (driver ioctl)
* - Link down : Port is going down
* - Early link up : Early link up detected
*/
/* external prototypes */
static int port_rx_setup_fsm_build_events(fsm_t *fsm);
static int _hal_port_rx_setup_state_init(fsm_t *fsm, int eventMsk, int isNewState);
static int _hal_port_rx_setup_state_start(fsm_t *fsm, int eventMsk, int isNewState);
static int _hal_port_rx_setup_state_reset_pcs(fsm_t *fsm, int ventMsk, int isNewState);
static int _hal_port_rx_setup_state_wait_lock(fsm_t *fsm, int ventMsk, int isNewState);
static int _hal_port_rx_setup_state_validate(fsm_t *fsm, int ventMsk, int isNewState);
static int _hal_port_rx_setup_state_restart(fsm_t *fsm, int ventMsk, int isNewState);
static int _hal_port_rx_setup_state_done(fsm_t *fsm, int ventMsk, int isNewState);
static fsm_state_table_entry_t port_rx_setup_fsm_states[] =
{
{ .state=HAL_PORT_RX_SETUP_STATE_INIT,
.stateName="INIT",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_init)
},
{ .state=HAL_PORT_RX_SETUP_STATE_START,
.stateName="START",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_start)
},
{ .state=HAL_PORT_RX_SETUP_STATE_RESET_PCS,
.stateName="RESET_PCS",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_reset_pcs)
},
{ .state=HAL_PORT_RX_SETUP_STATE_WAIT_LOCK,
.stateName="WAIT_LOCK",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_wait_lock)
},
{ .state=HAL_PORT_RX_SETUP_STATE_VALIDATE,
.stateName="VALIDATE",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_validate)
},
{ .state=HAL_PORT_RX_SETUP_STATE_RESTART,
.stateName="RESTART",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_restart)
},
{ .state=HAL_PORT_RX_SETUP_STATE_DONE,
.stateName="DONE",
FSM_SET_FCT_NAME(_hal_port_rx_setup_state_done)
},
{ .state=-1 }
};
static fsm_event_table_entry_t port_rx_setup_fsm_events[] = {
{
.evtMask = HAL_PORT_RX_SETUP_EVENT_TIMER,
.evtName="TIM"
},
{
.evtMask = HAL_PORT_RX_SETUP_EVENT_LINK_UP,
.evtName="LKUP"
},
{
.evtMask = HAL_PORT_RX_SETUP_EVENT_EARLY_LINK_UP,
.evtName="ELKUP"
},
{
.evtMask = HAL_PORT_RX_SETUP_EVENT_RX_ALIGNED,
.evtName="RX_ALGN"
},
{ .evtMask = -1 } };
//TODO-ML: the below structure is redundant, consider reogranization to use
// hal_port_rts_state
static struct rts_pll_state _pll_state;
static inline void updatePllState(struct hal_port_state * ps) {
// update PLL state once for all ports
rts_get_state(&_pll_state);
}
/* INIT state
* (Hypothesis: LPDC support has already been determined before )
*
* This state is used to start the minCalib RX timer
*/
static int _hal_port_rx_setup_state_init(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( ps->lpdc.globalLpdc->numberOfLpdcPorts ) {
if (ps->lpdc.isSupported) {
if ( _isHalRxSetupEventEarlyLinkUp(eventMsk) )
libwr_tmo_restart(&ps->lpdc.minCalibRx_timeout);
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_START);
} else {
if ( _isHalRxSetupEventLinkUp(eventMsk))
libwr_tmo_restart(&ps->lpdc.minCalibRx_timeout);
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_START);
}
} else
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_START);
return 0;
}
/* START state
* (Hypothesis: LPDC support has already been determined before )
*
* if if LPDC
* start the LPDC process
* else
* nothing to do, go to DONE and wait for link up
* fi
*/
static int _hal_port_rx_setup_state_start(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
halPortLpdcRx_t *rxSetup=ps->lpdc.rxSetup;
// prevent RX FSM from starting up when the TX path calibration of the port is
// not completed.
int tx_setup_state = fsm_get_state( &ps->lpdc.txSetupFSM);
if( tx_setup_state != HAL_PORT_TX_SETUP_STATE_DONE) {
pr_warning("rx_setup FSM is attempted to be started before the"
" tx_setup FSM has finished (in state %d) - this should"
" never happen, in theory.\n",
tx_setup_state);
return 0;
}
if (ps->lpdc.isSupported) {
if (isNewState)
// Restart the time-out
libwr_tmo_restart(&rxSetup->earlyup_timeout);
/* Wait a bit to make sure early_link_up is reseted. This
timeout is initialized in hal_port_rx_setup_init_fsm(),
see detailed description there. */
if (!libwr_tmo_expired(&rxSetup->earlyup_timeout)) {
return 0;
}
// LPDC support
pcs_writel(ps, MDIO_LPC_CTRL_TX_ENABLE |
MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK,
MDIO_LPC_CTRL);
if (_isHalRxSetupEventEarlyLinkUp(eventMsk)) {
halPortLpdcRx_t *rxSetup = ps->lpdc.rxSetup;
rxSetup->attempts = 0;
rts_enable_ptracker(ps->hw_index, 0);
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_RESET_PCS);
} else {
// Restart the time-out
libwr_tmo_restart(&rxSetup->earlyup_timeout);
}
} else {
/* nothing to do, go waiting for link_up*/
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_DONE);
}
return 0;
}
/*
* RESET_PCS state
*
*/
static int _hal_port_rx_setup_state_reset_pcs(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
halPortLpdcRx_t *rxSetup=ps->lpdc.rxSetup;
if ( isNewState )
libwr_tmo_restart(&rxSetup->link_timeout);
if( _isHalRxSetupEventEarlyLinkUp(eventMsk)) {
halPortLpdcRx_t *rxSetup=ps->lpdc.rxSetup;
pcs_writel(ps, MDIO_LPC_CTRL_RESET_RX |
MDIO_LPC_CTRL_TX_ENABLE |
MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK,
MDIO_LPC_CTRL);
shw_udelay(1);
pcs_writel(ps, MDIO_LPC_CTRL_TX_ENABLE |
MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK,
MDIO_LPC_CTRL);
rxSetup->attempts++;
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_WAIT_LOCK);
} else {
if( libwr_tmo_expired( &rxSetup->link_timeout ) )
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_INIT);
}
return 0;
}
/*
* WAIT_LOCK state
*
*/
static int _hal_port_rx_setup_state_wait_lock(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
halPortLpdcRx_t *rxSetup=ps->lpdc.rxSetup;
if ( isNewState ) {
libwr_tmo_restart(&rxSetup->link_timeout);
libwr_tmo_restart(&rxSetup->align_timeout);
}
if ( _isHalRxSetupEventEarlyLinkUp(eventMsk)) {
// 1ms rx align detection window, described in previous state.
if(! libwr_tmo_expired(&rxSetup->align_timeout) )
return 0; // call me again 1ms later...
if ( _isHalRxSetupEventRxAligned(eventMsk)) {
rts_enable_ptracker(ps->hw_index, 0);
rts_enable_ptracker(ps->hw_index, 1);
_pll_state.channels[ps->hw_index].flags = 0;
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_VALIDATE);
} else {
if ( libwr_tmo_expired ( &ps->lpdc.minCalibRx_timeout))
// We are looping from WAIT_LOCK and RESET_PCS for too long
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_INIT);
else
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_RESET_PCS);
}
} else {
if( libwr_tmo_expired( &rxSetup->link_timeout ) )
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_INIT);
}
return 0;
}
/*
* VALIDATE state
*
*/
static int _hal_port_rx_setup_state_validate(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
updatePllState(ps);
if (_pll_state.channels[ps->hw_index].flags & CHAN_PMEAS_READY) {
int phase = _pll_state.channels[ps->hw_index].phase_loopback;
halPortLpdcRx_t *rxSetup=ps->lpdc.rxSetup;
pcs_writel(ps, MDIO_LPC_CTRL_RX_ENABLE |
MDIO_LPC_CTRL_TX_ENABLE |
MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK,
MDIO_LPC_CTRL);
pcs_writel(ps, BMCR_ANENABLE | BMCR_ANRESTART, MII_BMCR);
pr_info("wri%d: RX calibration complete at phase %d "
"ps (after %d attempts).\n", ps->hw_index + 1,
phase, rxSetup->attempts);
rts_enable_ptracker(ps->hw_index, 0);
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_DONE);
} else {
if ( !_isHalRxSetupEventEarlyLinkUp(eventMsk) || libwr_tmo_expired( &ps->lpdc.minCalibRx_timeout) )
// We are waiting for too long
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_INIT);
}
return 0;
}
/*
* RESTART state - wait few ms before to mode to START state
*
*
*/
static int _hal_port_rx_setup_state_restart(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState ) {
// This timer is used to leave enough time to the FSM in the other side to detect a link down
libwr_tmo_restart(&ps->lpdc.rxSetup->restart_timeout);
pcs_writel(ps, MDIO_LPC_CTRL_DMTD_SOURCE_TXOUTCLK,
MDIO_LPC_CTRL);
} else {
if( libwr_tmo_expired( &ps->lpdc.rxSetup->restart_timeout ) ) {
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_INIT);
}
}
return 0;
}
/*
* DONE state - wait for link_up
*
* if LPDC supported
* if early_link_down event then state=START
* if link up event then return final state machine reached
*
*/
static int _hal_port_rx_setup_state_done(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
int early_up = _isHalRxSetupEventEarlyLinkUp(eventMsk);
int link_up = _isHalRxSetupEventLinkUp(eventMsk);
/* earlyLinkUp detection only if LPDC support */
if ( ps->lpdc.isSupported ) {
if ( isNewState ) {
libwr_tmo_restart(&ps->lpdc.rxSetup->align_to_link_timeout);
}
if ( !early_up) {
// Port went done
pr_info("rxcal: early link flag lost on port wri%d\n",
ps->hw_index + 1);
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_INIT);
return 0;
}
if( libwr_tmo_expired( &ps->lpdc.rxSetup->align_to_link_timeout ) && !link_up)
{
pr_warning("rxcal: link is fucked up. Retrying calibration on port %d\n",ps->hw_index + 1);
fsm_fire_state(fsm, HAL_PORT_RX_SETUP_STATE_RESTART);
return 0;
}
}
if ( ps->lpdc.globalLpdc->numberOfLpdcPorts )
return link_up && libwr_tmo_expired(&ps->lpdc.minCalibRx_timeout) ? 1 : 0;
else
return link_up ? 1 : 0;
}
/* Build FSM events */
static int port_rx_setup_fsm_build_events (fsm_t *fsm) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
int portEventMask=HAL_PORT_RX_SETUP_EVENT_TIMER;
if ( ps->evt_linkUp > 0 ) {
portEventMask |= HAL_PORT_RX_SETUP_EVENT_LINK_UP;
}
if ( ps->lpdc.isSupported ) {
uint32_t mioLpcStat;
if ( pcs_readl(ps, MDIO_LPC_STAT,&mioLpcStat) >= 0 ) {
if (mioLpcStat & MDIO_LPC_STAT_LINK_UP)
portEventMask |= HAL_PORT_RX_SETUP_EVENT_EARLY_LINK_UP;
if (mioLpcStat & MDIO_LPC_STAT_LINK_ALIGNED)
portEventMask |= HAL_PORT_RX_SETUP_EVENT_RX_ALIGNED;
}
}
return portEventMask;
}
/* Initialize rx_setup - this is a global init, executed once for all ports/FSMs.
*/
void hal_port_rx_setup_init_all(struct hal_port_state * ports) {
char name[64];
int index;
for (index = 0; index < HAL_MAX_PORTS; index++){
struct hal_port_state *ps = &ports[index];
snprintf(name, sizeof(name), "PortRxSetupFSM.%d", ps->hw_index);
if (fsm_generic_create(&ps->lpdc.rxSetupFSM, name,
port_rx_setup_fsm_build_events, port_rx_setup_fsm_states,
port_rx_setup_fsm_events, ps)) {
pr_error("Cannot create RxSetup fsm !\n");
exit(EXIT_FAILURE);
}
}
}
/*
* Init RX SETUP FSM
*/
void hal_port_rx_setup_fsm_init(struct hal_port_state * ps ) {
fsm_init_state ( &ps->lpdc.rxSetupFSM);
if ( ps->lpdc.isSupported ) {
/* This timeout is needed when link goes down. In such case
the link_down flag is set earlier than the early_link_up
flag is reseted. So, after the link goes down, we need to
wait some time before executing the rx_setup FSM. Without
such a wait, after unplugging fiber, the rx_setup FSM is
started and then hangs in in wait_lock state until
link_timeout fires.
NOTE: We do the initialization of timeout here (and not in
the _hal_port_rx_setup_state_start() when isNewState=1) for
a reason. If it was done in _hal_port_rx_setup_state_start(),
the timeout would also kick in when the START state is
entered from WAIT_LOCK*/
halPortLpdcRx_t *rxSetup = ps->lpdc.rxSetup;
libwr_tmo_init(&rxSetup->earlyup_timeout, 10, 0);
// link timeout
libwr_tmo_init(&rxSetup->link_timeout, 100, 0);
// Establish a 1ms wait for the LINK_ALIGNED flag -
// alignment detection takes a little bit more time than early
// link detect. Without the wait (depending on execution timing of the HAL code)
// the wait_lock state might detect the early link but never see it's aligned.
libwr_tmo_init(&rxSetup->align_timeout, 1, 0);
// Align to link time-out
libwr_tmo_init( &ps->lpdc.rxSetup->align_to_link_timeout, 5000, 0 );
// This timer is used to leave enough time to the FSM in the other side to detect a link down
libwr_tmo_init(&ps->lpdc.rxSetup->restart_timeout, 100, 0);
}
/**
* This time-out is used to impose the same minimum of RX calibration time
* on all ports (including port without LPDC. This is done to try to have
* all ports going to state UP at the same time.
* It is is not done, PPSi (with BMCA) will take a long time to stabilize
* its port states
*/
libwr_tmo_init(&ps->lpdc.minCalibRx_timeout,20000,0); // Timeout set to 20s
fsm_fire_state( &ps->lpdc.rxSetupFSM, HAL_PORT_RX_SETUP_STATE_INIT );
}
/* FSM state machine for RX setup on a given port
* Returned value:
* 1: when final state has been reached
* 0: when final state has not been reached
* -1: error detected
*/
int hal_port_rx_setup_fsm_run( struct hal_port_state * ps ) {
if ( !ps->in_use )
return 1;
return fsm_generic_run( &ps->lpdc.rxSetupFSM );
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_RX_H
#define HAL_PORT_FSM_RX_H
/* prototypes */
void hal_port_rx_setup_fsm_init(struct hal_port_state * ps );
int hal_port_rx_setup_fsm_run( struct hal_port_state * ps );
void hal_port_rx_setup_fsm_reset(struct hal_port_state * ps );
void hal_port_rx_setup_init_all(struct hal_port_state * ports);
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_RXP_H
#define HAL_PORT_FSM_RXP_H
#include <libwr/wrs-msg.h>
#include <libwr/generic_fsm.h>
typedef enum {
HAL_PORT_RX_SETUP_STATE_INIT=0,
HAL_PORT_RX_SETUP_STATE_START,
HAL_PORT_RX_SETUP_STATE_RESET_PCS,
HAL_PORT_RX_SETUP_STATE_WAIT_LOCK,
HAL_PORT_RX_SETUP_STATE_VALIDATE,
HAL_PORT_RX_SETUP_STATE_RESTART,
HAL_PORT_RX_SETUP_STATE_DONE
} hapPortRxSetupState_t;
typedef enum
{
HAL_PORT_RX_SETUP_EVENT_TIMER=(1<<0),
HAL_PORT_RX_SETUP_EVENT_LINK_UP=(1<<1),
HAL_PORT_RX_SETUP_EVENT_EARLY_LINK_UP=(1<<2),
HAL_PORT_RX_SETUP_EVENT_RX_ALIGNED=(1<<3)
}halPortRxSetupEventMask_t ;
static inline int _isHalRxSetupEventTimer(halPortRxSetupEventMask_t eventMsk) {
return eventMsk & HAL_PORT_RX_SETUP_EVENT_TIMER;
}
static inline int _isHalRxSetupEventLinkUp(halPortRxSetupEventMask_t eventMsk) {
return eventMsk & HAL_PORT_RX_SETUP_EVENT_LINK_UP;
}
static inline int _isHalRxSetupEventEarlyLinkUp(halPortRxSetupEventMask_t eventMsk) {
return eventMsk & HAL_PORT_RX_SETUP_EVENT_EARLY_LINK_UP;
}
static inline int _isHalRxSetupEventRxAligned(halPortRxSetupEventMask_t eventMsk) {
return eventMsk & HAL_PORT_RX_SETUP_EVENT_RX_ALIGNED;
}
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#include <rt_ipc.h>
#include <libwr/hal_shmem.h>
#include <libwr/config-lpcalib.h>
#include <libwr/wrs-msg.h>
#include <libwr/generic_fsm.h>
#include "hal_exports.h"
#include "driver_stuff.h"
#include "hal_port_leds.h"
#include "hal_timing.h"
#include "hal_main.h"
#include "hal_ports.h"
#include "hal_port_fsm_txP.h"
#include "hal_port_fsm.h"
/**
* State machine
* States :
* - HAL_PORT_TX_SETUP_STATE_START:
* Inital state
* - .....
* - HAL_PORT_TX_SETUP_STATE_DONE:
* TX setup terminated
* Events :
* - timer : triggered regularly to execute background work
*/
/* external prototypes */
static int port_tx_setup_fsm_build_events(fsm_t *fsm);
static int port_tx_setup_fsm_state_start(fsm_t *fsm, int eventMsk, int isNewState);
static int port_tx_setup_fsm_state_validate(fsm_t *fsm, int eventMsk, int isNewState);
static int port_tx_setup_fsm_state_reset_pcs(fsm_t *fsm, int eventMsk, int isNewState);
static int port_tx_setup_fsm_state_wait_lock(fsm_t *fsm, int eventMsk, int isNewState);
static int port_tx_setup_fsm_state_measure_phase(fsm_t *fsm, int eventMsk, int isNewState);
static int port_tx_setup_fsm_state_done(fsm_t *fsm, int eventMsk, int isNewState);
static int port_tx_setup_fsm_state_wait_other_ports(fsm_t *fsm, int eventMsk, int isNewState);
static fsm_state_table_entry_t port_tx_setup_fsm_states[] =
{
{ .state=HAL_PORT_TX_SETUP_STATE_START,
.stateName="START",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_start)
},
{ .state=HAL_PORT_TX_SETUP_STATE_RESET_PCS,
.stateName="RESET_PCS",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_reset_pcs)
},
{ .state=HAL_PORT_TX_SETUP_STATE_MEASURE_PHASE,
.stateName="MEASURE_PHASE",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_measure_phase)
},
{ .state=HAL_PORT_TX_SETUP_STATE_WAIT_LOCK,
.stateName="WAIT_LOCK",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_wait_lock)
},
{ .state=HAL_PORT_TX_SETUP_STATE_VALIDATE,
.stateName="VALIDATE",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_validate)
},
{ .state=HAL_PORT_TX_SETUP_STATE_WAIT_OTHER_PORTS,
.stateName="WAIT OTHER PORTS",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_wait_other_ports)
},
{ .state=HAL_PORT_TX_SETUP_STATE_DONE,
.stateName="DONE",
FSM_SET_FCT_NAME(port_tx_setup_fsm_state_done)
},
{.state=-1}
};
static fsm_event_table_entry_t port_tx_setup_fsm_events[] = {
{
.evtMask = HAL_PORT_TX_SETUP_EVENT_TIMER,
.evtName="TIM"
},
{ .evtMask = -1 } };
//TODO-ML: the below structure is redundant, consider reogranization to use
// hal_port_rts_state
static struct rts_pll_state _pll_state;
/* path to the file where Low Phase Drift calib parameters are stored */
static char *_calibrationFileName = "/update/tx_phase_cal.conf";
struct config_file *_calibrationConfig; // Calibration config form file
static inline void updatePllState(struct hal_port_state * ps) {
// update PLL state once for all ports
rts_get_state(&_pll_state);
}
static inline void txSetupDone(struct hal_port_state * ps) {
halGlobalLPDC_t * gl = ps->lpdc.globalLpdc;
gl->maskTxSetupDonePorts|=((uint32_t)1)<<ps->hw_index;
}
static inline void txSetupNotDone(struct hal_port_state * ps) {
halGlobalLPDC_t * gl = ps->lpdc.globalLpdc;
gl->maskTxSetupDonePorts&=~(((uint32_t)1)<<ps->hw_index);
}
static inline int txSetupDoneOnAllPorts(struct hal_port_state * ps) {
halGlobalLPDC_t * gl = ps->lpdc.globalLpdc;
return gl->maskUsedPorts==gl->maskTxSetupDonePorts;
}
/* prototypes */
static void _write_tx_calibration_file(struct hal_port_state * ports);
static void _load_tx_calibration_file(struct hal_port_state * ports);
static int _within_range(int x, int minval, int maxval, int wrap);
/*
* START state
*
* If entering in state then
* Set LPDC supported accordingly to the hardware
* fi
* if LPDC is not supported then state=DONE
*
*/
static int port_tx_setup_fsm_state_start(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
// Force timing mode to FR
if ( hal_tmg_get_mode(NULL)!=HAL_TIMING_MODE_FREE_MASTER)
hal_tmg_set_mode(HAL_TIMING_MODE_FREE_MASTER);
ps->locked=0;
/* Disable TX light on this port */
shw_sfp_gpio_set(ps->hw_index, SFP_TX_DISABLE);
txSetupNotDone(ps);
if ( !ps->lpdc.isSupported ) {
// NO LPDC support
fsm_fire_state(fsm,HAL_PORT_TX_SETUP_STATE_WAIT_OTHER_PORTS);
return 0;
} else {
// LPDC support
halPortLpdcTx_t *txSetup=ps->lpdc.txSetup;
txSetup->attempts=0;
txSetup->expected_phase = 0;
txSetup->expected_phase_valid = 0;
txSetup->tolerance = TX_CAL_TOLERANCE;
txSetup->update_cnt = 0;
_pll_state.channels[ps->hw_index].flags = 0;
rts_enable_ptracker(ps->hw_index, 0);
rts_ptracker_set_average_samples(ps->hw_index, HAL_CAL_DMTD_SAMPLES);
pcs_writel(ps, MDIO_LPC_CTRL_RESET_RX |
MDIO_LPC_CTRL_DMTD_SOURCE_TXOUTCLK,
MDIO_LPC_CTRL);
led_set_wrmode(ps->hw_index,SFP_LED_WRMODE_TX_CALIB);
fsm_fire_state(fsm, HAL_PORT_TX_SETUP_STATE_RESET_PCS);
}
return 0;
}
/*
* RESET_PCS state
*
*
*/
static int port_tx_setup_fsm_state_reset_pcs(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
rts_enable_ptracker(ps->hw_index, 0);
pcs_writel(ps, MDIO_LPC_CTRL_RESET_TX |
MDIO_LPC_CTRL_RESET_RX |
MDIO_LPC_CTRL_DMTD_SOURCE_TXOUTCLK,
MDIO_LPC_CTRL);
shw_udelay(1);
pcs_writel(ps, MDIO_LPC_CTRL_RESET_RX |
MDIO_LPC_CTRL_DMTD_SOURCE_TXOUTCLK,
MDIO_LPC_CTRL);
fsm_fire_state(fsm,HAL_PORT_TX_SETUP_STATE_WAIT_LOCK);
return 0;
}
/*
* WAIT_LOCK state
*
*
*/
static int port_tx_setup_fsm_state_wait_lock(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
uint32_t value;
if ( pcs_readl(ps, MDIO_LPC_STAT,&value)>=0 ) {
if ( (value & MDIO_LPC_STAT_RESET_TX_DONE)!=0 ) {
ps->lpdc.txSetup->attempts++;
rts_enable_ptracker(ps->hw_index, 1);
_pll_state.channels[ps->hw_index].flags = 0;
fsm_fire_state(fsm, HAL_PORT_TX_SETUP_STATE_MEASURE_PHASE);
}
}
return 0;
}
/*
* MEASURE_PHASE state
*
*
*/
static int port_tx_setup_fsm_state_measure_phase(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
halPortLpdcTx_t *txSetup=ps->lpdc.txSetup;
updatePllState(ps);
if ( isNewState ) {
libwr_tmo_init(&txSetup->calib_timeout, TX_CAL_PHASE_MEAS_TIMEOUT, 1);
}
if (!(_pll_state.channels[ps->hw_index].flags & CHAN_PMEAS_READY)) {
if (libwr_tmo_expired(&txSetup->calib_timeout) )
{
pr_info("Port %d: tx phase measurement timeout expired,"
" retrying\n", ps->hw_index+1);
fsm_fire_state(fsm, HAL_PORT_TX_SETUP_STATE_RESET_PCS);
return 0; // retry
}
return 0; // keep waiting
}
txSetup = ps->lpdc.txSetup;
int phase = _pll_state.channels[ps->hw_index].phase_loopback;
txSetup->measured_phase = phase;
if (!txSetup->expected_phase_valid) {
if (txSetup->cal_saved_phase_valid) {
// got calibration file already? Aim EXACTLY for the phase
// bin we used in the first calibration
pr_info("Using phase from file :%d\n", txSetup->cal_saved_phase);
txSetup->expected_phase = txSetup->cal_saved_phase;
} else {
// First time calibrating? Give ourselves some freedom,
// let's say the first 1.5 ns of the 16 ns ref clock
// cycle, so that we have enough setup time
txSetup->tolerance = TX_CAL_FIRST_CAL_TOLERANCE;
txSetup->expected_phase = TX_CAL_FIRST_CAL_EXPECTED_PHASE;
}
txSetup->expected_phase_valid = 1;
}
int phase_min = txSetup->expected_phase - txSetup->tolerance;
int phase_max = txSetup->expected_phase + txSetup->tolerance;
// pr_info("TX Calibration: upd wri%d phase %d after %d "
// "attempts target %d tolerance %d\n",
// ps->hw_index+1, txSetup->measured_phase,
// txSetup->attempts, txSetup->expected_phase, txSetup->tolerance);
if(_within_range(phase, phase_min, phase_max, 16000)) {
pr_info("FIX port %d phase %d after %d attempts "
"(temp = %.3f degC)\n", ps->hw_index, txSetup->measured_phase,
txSetup->attempts, hal_get_fpga_temperature() / 256.0);
rts_enable_ptracker(ps->hw_index, 0);
rts_enable_ptracker(ps->hw_index, 1);
_pll_state.channels[ps->hw_index].flags = 0;
fsm_fire_state(fsm, HAL_PORT_TX_SETUP_STATE_VALIDATE);
} else
fsm_fire_state(fsm, HAL_PORT_TX_SETUP_STATE_RESET_PCS);
return 0;
}
/*
* Validate state
*
*
*/
static int port_tx_setup_fsm_state_validate(fsm_t *fsm, int eventMsk, int isNewState) {
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
halPortLpdcTx_t *txSetup;
updatePllState(ps);
if (!(_pll_state.channels[ps->hw_index].flags & CHAN_PMEAS_READY))
return 0; // keep waiting
txSetup = ps->lpdc.txSetup;
txSetup->measured_phase = _pll_state.channels[ps->hw_index].phase_loopback;
pr_info("Port %d: TX calibration complete\n", ps->hw_index + 1);
rts_enable_ptracker(ps->hw_index, 0);
// enable the PCS on the port
pcs_writel(ps, MDIO_LPC_CTRL_RESET_RX |
MDIO_LPC_CTRL_TX_ENABLE |
MDIO_LPC_CTRL_DMTD_SOURCE_RXRECCLK,
MDIO_LPC_CTRL);
led_set_wrmode(ps->hw_index,SFP_LED_WRMODE_OFF);
fsm_fire_state(fsm, HAL_PORT_TX_SETUP_STATE_WAIT_OTHER_PORTS);
return 0;
}
/*
* Wait for all LPDC-supporting ports to be calibrated
*/
static int port_tx_setup_fsm_state_wait_other_ports(fsm_t *fsm, int eventMsk, int isNewState)
{
struct hal_port_state * ps = (struct hal_port_state*) fsm->priv;
if ( isNewState )
txSetupDone(ps);
if (txSetupDoneOnAllPorts(ps) ) {
if (ps->lpdc.globalLpdc->numberOfLpdcPorts )
_write_tx_calibration_file(ps);
/* Enable TX light on this port */
shw_sfp_gpio_set(ps->hw_index, 0);
fsm_fire_state(fsm,HAL_PORT_TX_SETUP_STATE_DONE);
}
return 0;
}
/*
* DONE state
*
* Return final state machine reached
*/
static int port_tx_setup_fsm_state_done(fsm_t *fsm, int eventMsk, int isNewState) {
return 1; /* Final state reached */
}
/* Build events mask */
static int port_tx_setup_fsm_build_events(fsm_t *fsm) {
return HAL_PORT_TX_SETUP_EVENT_TIMER;
}
/* Initialize tx_setup-this is a global init, executed once for all ports/FSMs.
It includes:
- loading calibration file if exists
- initializing a global structure and filling it in - this structure is
needed by tx calibration only (so far)
*/
void hal_port_tx_setup_init_all(struct hal_port_state * ports, halGlobalLPDC_t *globalLpdc) {
int index;
int numberOfLpdcPorts = 0;
uint32_t maskUsedPorts=0;
int firstLpdcPort = -1;
int lastLpdcPort = -1;
char name[64];
/* Initialize pointer to the global structure for each port.
Check whether there is any port that supports LPDC,
if there is/are such port(s), remember index of the first/last and
their number. We need this info in operation.*/
for (index = 0; index < HAL_MAX_PORTS; index++){
struct hal_port_state *ps = &ports[index];
if(ps->in_use ) {
/* Link the global structure from each port.
NOTE: Even ports that do not support LPDC require access to
this global structure as they need to know whether all
the LPDC-supporting ports have been calibrated */
ps->lpdc.globalLpdc = globalLpdc;
maskUsedPorts|=((uint32_t)1)<<ps->hw_index;
/* Fill in global info needed for operation */
if (ps->lpdc.isSupported) {
/* if this is he first supported port, save its index*/
if (firstLpdcPort < 0)
firstLpdcPort = index;
/*remember the index, just in case it is the last LPDC port*/
lastLpdcPort = index;
/* count number of supported ports*/
numberOfLpdcPorts++;
}
/* Fill txSetup fsm structure */
snprintf(name, sizeof(name), "PortTxSetupFSM.%d", ps->hw_index);
if (fsm_generic_create(&ps->lpdc.txSetupFSM, name,
port_tx_setup_fsm_build_events, port_tx_setup_fsm_states,
port_tx_setup_fsm_events, ps)) {
pr_error("Cannot create TxSetup fsm !\n");
exit(EXIT_FAILURE);
}
}
}
globalLpdc->maskUsedPorts=maskUsedPorts;
/* if there are any LPDC ports, do some preparation */
if(globalLpdc && numberOfLpdcPorts) {
/* fill in the global structure */
globalLpdc->numberOfLpdcPorts = numberOfLpdcPorts;
globalLpdc->maskTxSetupDonePorts = 0;
globalLpdc->calFileSynced = 0;
globalLpdc->firstLpdcPort = firstLpdcPort;
globalLpdc->lastLpdcPort = lastLpdcPort;
pr_info("WR switch supports LPDC on %d ports ("
"first port is %d, last port is %d)\n",
globalLpdc->numberOfLpdcPorts,
globalLpdc->firstLpdcPort,
globalLpdc->lastLpdcPort);
/* load the calib file*/
_load_tx_calibration_file(ports);
/* Force going to FREE running master for the calibration */
if ( hal_tmg_get_mode(NULL)!=HAL_TIMING_MODE_FREE_MASTER)
hal_tmg_set_mode(HAL_TIMING_MODE_FREE_MASTER);
}
}
//static void hal_port_tx_setup_fsm_reset(struct hal_port_state * ps )
//{
// fsm_set_state( &ps->lpdc.txSetupFSM, -1 ); // reset state
// fsm_fire_state( &ps->lpdc.txSetupFSM, HAL_PORT_TX_SETUP_STATE_START );
//
// if ( hal_tmg_get_mode(NULL)==HAL_TIMING_MODE_BC)
// hal_tmg_set_mode(HAL_TIMING_MODE_FREE_MASTER);
// ps->locked=0;
//
// pcs_writel(ps, MDIO_LPC_CTRL_RESET_RX |
// MDIO_LPC_CTRL_DMTD_SOURCE_TXOUTCLK,
// MDIO_LPC_CTRL);
//}
/* Init the TX SETUP FSM on a given port */
void hal_port_tx_setup_fsm_init(struct hal_port_state * ps ) {
fsm_init_state(&ps->lpdc.txSetupFSM);
fsm_fire_state(&ps->lpdc.txSetupFSM,HAL_PORT_TX_SETUP_STATE_START);
}
/* FSM state machine for TX setup on a given port
* Returned value:
* 1: when final state has been reached
* 0: when final state has not been reached
* -1: error detected
*/
int hal_port_tx_setup_fsm_run( struct hal_port_state * ps )
{
if ( !ps->in_use )
return 1;
return fsm_generic_run(&ps->lpdc.txSetupFSM);
}
/* if config is present then update the calibration data */
static void _load_tx_calibration_file(struct hal_port_state * ports) {
int i = 0;
halGlobalLPDC_t * gl = ports[0].lpdc.globalLpdc;
// Read calibration file, if it exists
_calibrationConfig = cfg_load(_calibrationFileName, 0);
if ( !_calibrationConfig ) {
pr_info("Can't load TX phase calibration data file: %s\n",
_calibrationFileName);
gl->calFileSynced = 0;
return;
}
pr_info("Loading LPCD config data from %s\n", _calibrationFileName);
for (i = 0; i < HAL_MAX_PORTS; i++){
struct hal_port_state *ps = &ports[i];
if (ps->in_use && ps->lpdc.isSupported)
{
char key_name[80];
int value;
snprintf(key_name, sizeof(key_name), "TX_PHASE_PORT%d",
ps->hw_index);
if(cfg_get_int( _calibrationConfig, key_name, &value) )
{
pr_info("cal: wri%d %d\n", ps->hw_index+1, value);
ps->lpdc.txSetup->cal_saved_phase = value;
ps->lpdc.txSetup->cal_saved_phase_valid = 1;
}
}
}
gl->calFileSynced = 1;
cfg_close(_calibrationConfig);
}
static int file_exists(const char *filename)
{
FILE *f = fopen(filename, "r");
if (f != NULL)
{
fclose(f);
return 1;
}
return 0;
}
static void _write_tx_calibration_file(struct hal_port_state * ps)
{
int i;
halGlobalLPDC_t * gl = ps->lpdc.globalLpdc;
/* Only the first LPDC-supporting port writes the file. Otherwise,
there is problem with pointers when looping through port structures
to write data for all ports, see the "for" below. */
if(gl->firstLpdcPort != ps->hw_index)
return;
if(gl->calFileSynced)
return;
if (file_exists(_calibrationFileName))
{
pr_warning("Tx calibration file exists, yet it has not been"
" synched. Something seems wrong. Should not get here\n");
return;
}
struct config_file *cfg = cfg_load(_calibrationFileName, 1);
struct hal_port_state *_ps=ps;
for (i = gl->firstLpdcPort; i <= gl->lastLpdcPort; i++) {
if (_ps->in_use && _ps->lpdc.isSupported)
{
char key_name[80];
snprintf(key_name, sizeof(key_name), "TX_PHASE_PORT%d", ps->hw_index);
cfg_set_int(cfg, key_name, ps->lpdc.txSetup->measured_phase);
}
_ps++;
}
pr_info( "Writing TX phase calibration data to %s\n", _calibrationFileName );
cfg_save( cfg, _calibrationFileName );
cfg_close(cfg);
gl->calFileSynced = 1;
}
//min -269 max 331 x 9631 Rv 1
static int _within_range(int x, int minval, int maxval, int wrap)
{
int rv;
while (maxval >= wrap)
maxval -= wrap;
while (maxval < 0)
maxval += wrap;
while (minval >= wrap)
minval -= wrap;
while (minval < 0)
minval += wrap;
while (x < 0)
x += wrap;
while (x >= wrap)
x -= wrap;
if (maxval > minval)
rv = (x >= minval && x <= maxval) ? 1 : 0;
else
rv = (x >= minval || x <= maxval) ? 1 : 0;
return rv;
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_TX_H
#define HAL_PORT_FSM_TX_H
void hal_port_tx_setup_init_all(struct hal_port_state * ports, halGlobalLPDC_t *globalLpdc);
void hal_port_tx_setup_fsm_init(struct hal_port_state * ps );
int hal_port_tx_setup_fsm_run( struct hal_port_state * ps );
void hal_port_tx_setup_fsm_reset(struct hal_port_state * ps );
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_FSM_TXP_H
#define HAL_PORT_FSM_TXP_H
#include <libwr/wrs-msg.h>
#include <libwr/generic_fsm.h>
#define TX_CAL_PHASE_MEAS_TIMEOUT 1000 /* ms */
#define TX_CAL_TOLERANCE 300 /* ps */
#define TX_CAL_FIRST_CAL_TOLERANCE 750 /* ps */
#define TX_CAL_FIRST_CAL_EXPECTED_PHASE 750 /* ps */
typedef enum {
HAL_PORT_TX_SETUP_STATE_START=0,
HAL_PORT_TX_SETUP_STATE_RESET_PCS,
HAL_PORT_TX_SETUP_STATE_WAIT_LOCK,
HAL_PORT_TX_SETUP_STATE_MEASURE_PHASE,
HAL_PORT_TX_SETUP_STATE_VALIDATE,
HAL_PORT_TX_SETUP_STATE_WAIT_OTHER_PORTS,
HAL_PORT_TX_SETUP_STATE_DONE
} halPortTxSetupState_t;
typedef enum
{
HAL_PORT_TX_SETUP_EVENT_TIMER=(1<<0),
}halPortTxSetupEventMask_t ;
static inline int _isHalTxSetupEventTimer(halPortTxSetupEventMask_t eventMsk) {
return eventMsk & HAL_PORT_TX_SETUP_EVENT_TIMER;
}
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#include <hal_exports.h>
#include <libwr/hal_shmem.h>
#include "hal_ports.h"
#include "hal_port_leds.h"
static struct Leds {
unsigned char sync_leds_map_to_update[HAL_MAX_PORTS];
unsigned char link_leds_map_to_update[HAL_MAX_PORTS];
unsigned char sync_leds_map[HAL_MAX_PORTS];
unsigned char link_leds_map[HAL_MAX_PORTS];
} _leds;
static int led_blink_state = 0;
/* flip state of the leds to blink */
void led_blink_state_change(void) {
led_blink_state = 1 - led_blink_state;
}
/* state of blinking led (on/off) */
int led_get_blink_state(void) {
return led_blink_state;
}
void led_init_all_ports(struct hal_port_state *ps ) {
int i;
// Clear data
memset(&_leds,0,sizeof(_leds));
for (i = 0; i < HAL_MAX_PORTS; i++) {
shw_sfp_set_led_synced(i, 0);
shw_sfp_set_generic(i, 0, SFP_LED_WRMODE1 | SFP_LED_WRMODE2);
}
}
/* to avoid i2c transfers to set the link LEDs, cache their state */
void led_set_wrmode(int portIndex, int value)
{
if (portIndex >= HAL_MAX_PORTS)
return;
_leds.link_leds_map_to_update[portIndex]=value;
}
void led_link_update(struct hal_port_state *ps) {
int i;
for (i = 0; i < HAL_MAX_PORTS; i++) {
unsigned char value=_leds.link_leds_map_to_update[i];
if ( value != _leds.link_leds_map[i] ||
value == SFP_LED_WRMODE_TX_CALIB /* always update to blink*/) {
_leds.link_leds_map[i]=value;
/* update the LED, don't forget to turn off LEDs if needed */
switch (value) {
case SFP_LED_WRMODE_SLAVE :
/* cannot set and clear LED in the same call! */
shw_sfp_set_generic(i, 1, SFP_LED_WRMODE1);
shw_sfp_set_generic(i, 0, SFP_LED_WRMODE2);
break;
case SFP_LED_WRMODE_OTHER :
/* cannot set and clear LED in the same call! */
shw_sfp_set_generic(i, 0, SFP_LED_WRMODE1);
shw_sfp_set_generic(i, 1, SFP_LED_WRMODE2);
break;
case SFP_LED_WRMODE_MASTER:
shw_sfp_set_generic(i, 1,SFP_LED_WRMODE1 | SFP_LED_WRMODE2);
break;
case SFP_LED_WRMODE_TX_CALIB:
if(led_get_blink_state()){ // SFP_LED_WRMODE_OTHER
shw_sfp_set_generic(i, 0, SFP_LED_WRMODE1);
shw_sfp_set_generic(i, 1, SFP_LED_WRMODE2);
}
else { // SFP_LED_WRMODE_OFF
shw_sfp_set_generic(i, 0,
SFP_LED_WRMODE1 | SFP_LED_WRMODE2);
}
break;
case SFP_LED_WRMODE_OFF :
shw_sfp_set_generic(i, 0,
SFP_LED_WRMODE1 | SFP_LED_WRMODE2);
break;
}
}
#if 0
if (port->in_use && state_up(port->state)) {
if (port->portMode == PORT_MODE_SLAVE)
set_led_wrmode(i, SFP_LED_WRMODE_SLAVE);
else if (port->portMode == PORT_MODE_MASTER)
set_led_wrmode(i, SFP_LED_WRMODE_MASTER);
else
set_led_wrmode(i, SFP_LED_WRMODE_OTHER);
}
port++;
#endif
}
led_blink_state_change();
}
/* to avoid i2c transfers to set the synced LEDs, cache their state */
void led_set_synched(int portIndex, int value)
{
if (portIndex >= HAL_MAX_PORTS)
return;
_leds.sync_leds_map_to_update[portIndex] = value;
}
void led_synched_update(struct hal_port_state *ps )
{
int i;
for (i = 0; i < HAL_MAX_PORTS; i++) {
#if 0
/* Check:
* --port in use
* --link is up
*/
if ( ps->in_use
&& state_up(ps->state) ) {
int ledValue;
ledValue= ps->synchronized
&& (ps->portInfoUpdated--) > -10
? 1 : 0;
set_led_synced(i, ledValue);
}
#else
unsigned char value=_leds.sync_leds_map_to_update[i];
if ( value != _leds.sync_leds_map[i]) {
// Update led
shw_sfp_set_led_synced(i, (int)value);
_leds.sync_leds_map[i] = value;
}
ps++;
#endif
}
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORT_LEDS_H
#define HAL_PORT_LEDS_H
#define SFP_LED_WRMODE_SLAVE (1) /* green */
#define SFP_LED_WRMODE_OTHER (2) /* orange */
#define SFP_LED_WRMODE_MASTER (3) /* yellow */
#define SFP_LED_WRMODE_OFF (4) /* to off entire WRMODE LED */
#define SFP_LED_WRMODE_TX_CALIB (5) /* LPDC Tx calibration */
/* Prototypes */
extern void led_blink_state_change(void);
extern void led_init_all_ports(struct hal_port_state *ps );
extern void led_set_wrmode(int portIndex, int val);
extern void led_link_update(struct hal_port_state *port);
extern void led_set_synched(int portIndex, int val);
extern void led_synched_update(struct hal_port_state *port );
#endif
......@@ -16,6 +16,7 @@
#include <linux/if.h>
/* LOTs of hardware includes */
#include <rt_ipc.h>
#include <libwr/switch_hw.h>
#include <libwr/wrs-msg.h>
#include <libwr/pio.h>
......@@ -23,75 +24,84 @@
#include <libwr/shmem.h>
#include <libwr/config.h>
#include <libwr/timeout.h>
#include <libwr/hal_shmem.h>
#include <ppsi/ppsi.h>
#include "wrsw_hal.h"
#include <rt_ipc.h>
#include <hal_exports.h>
#include <libwr/hal_shmem.h>
#include "driver_stuff.h"
#define UPDATE_RTS_PERIOD 250 /* ms */
#define UPDATE_SFP_PERIOD 1000 /* ms */
#define UPDATE_SYNC_LEDS_PERIOD 500 /* ms */
#define UPDATE_LINK_LEDS_PERIOD 500 /* ms */
#define UPDATE_SFP_DOM_PERIOD 1000 /* ms */
typedef struct {
struct pp_instance * ppi; /* pointer to the ppi instance */
struct pp_servo servo_snapshot; /* image of a the ppsi servo */
} inst_servo_t ;
#include "hal_exports.h"
#include "hal_timer.h"
#include "hal_port_fsm.h"
#include "hal_port_fsm_tx.h"
#include "hal_port_leds.h"
#include "hal_ports.h"
#include "hal_timing.h"
extern struct hal_shmem_header *hal_shmem;
extern struct wrs_shm_head *hal_shmem_hdr;
/* Port table: the only item which is not "hal_port_*", as it's much used */
static struct hal_port_state *ports;
#define FSM_DEBUG 0
/* An fd of always opened raw sockets for ioctl()-ing Ethernet devices */
static int hal_port_fd;
/* RT subsystem PLL state, polled regularly via mini-ipc */
struct rts_pll_state hal_port_rts_state;
int hal_port_rts_state_valid = 0;
hal_ports_t halPorts;
/* Polling timeouts (RT Subsystem & SFP detection) */
static timeout_t hal_port_tmo_rts, hal_port_tmo_sfp;
static timeout_t update_sync_leds_tmo, update_link_leds_tmo;
static timeout_t update_sfp_dom_tmo;
static int hal_port_nports;
/**
* End new stuff
*/
int hal_port_check_lock(const char *port_name);
static void update_link_leds(void);
static void set_led_wrmode(int p_index, int val);
static void set_led_synced(int p_index, int val);
static void update_sync_leds(void);
typedef enum {
TMO_PORT_TMO_RTS=0,
TMO_PORT_POLL_SFP,
TMO_PORT_SFP_DOM,
TMO_UPDATE_SYNC_LEDs,
TMO_UPDATE_LINK_LEDS,
TMO_COUNT
}port_tmo_id_t;
static void _cb_port_poll_rts_state(int timerId);
static void _cb_port_poll_sfp(int timerId);
static void _cb_port_poll_sfp_dom(int timerId);
static void _cb_port_update_sync_leds(int timerId);
static void _cb_port_update_link_leds(int timerId);
int hal_port_any_locked(void)
{
if (!hal_port_rts_state_valid)
return -1;
if (hal_port_rts_state.current_ref == REF_NONE)
return -1;
return hal_port_rts_state.current_ref;
}
/* Polling timeouts (RT Subsystem & SFP detection) */
static timer_parameter_t _timerParameters[] = {
{
.id=TMO_PORT_TMO_RTS,
.tmoMs=250, // 250 ms
.repeat=1,
.cb=_cb_port_poll_rts_state
},
{
.id=TMO_PORT_POLL_SFP,
.tmoMs=1000, // 1s
.repeat=1,
.cb=_cb_port_poll_sfp
},
{
.id=TMO_PORT_SFP_DOM,
.tmoMs=1000, // 1s
.repeat=1,
.cb=_cb_port_poll_sfp_dom
},
{
.id=TMO_UPDATE_SYNC_LEDs,
.tmoMs= 500, // 500ms
.repeat=1,
.cb=_cb_port_update_sync_leds
},
{
.id=TMO_UPDATE_LINK_LEDS,
.tmoMs=500, // 500ms
.repeat=1,
.cb=_cb_port_update_link_leds
},
};
/* Resets the state variables of a port and re-starts its state machines */
static void hal_port_reset_state(struct hal_port_state * p)
{
p->state = HAL_PORT_STATE_LINK_DOWN;
p->calib.rx_calibrated =
p->calib.tx_calibrated =
p->locked = 0;
p->lock_state =
p->tx_cal_pending =
p->rx_cal_pending = 0;
p->portMode= PORT_MODE_OTHER;
p->synchronized=0;
#define PORT_TIMER_COUNT (sizeof(_timerParameters)/sizeof(timer_parameter_t))
}
/* prototypes */
static int hal_port_check_lpdc_support(struct hal_port_state * ps);
/* checks if the port is supported by the FPGA firmware */
static int hal_port_check_presence(const char *if_name, unsigned char *mac)
......@@ -100,16 +110,15 @@ static int hal_port_check_presence(const char *if_name, unsigned char *mac)
strncpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
if (ioctl(hal_port_fd, SIOCGIFHWADDR, &ifr) < 0)
if (ioctl(halPorts.hal_port_fd, SIOCGIFHWADDR, &ifr) < 0)
return 0;
memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
return 1;
}
/* Port initialization, from dot-config values */
static int hal_port_init(int index)
static int hal_port_init(struct hal_port_state *ps, int index)
{
struct hal_port_state *p = &ports[index];
char key[128];
int port_i;
char *retValue;
......@@ -118,52 +127,51 @@ static int hal_port_init(int index)
/* index is 0..17, port_i 1..18 */
port_i = index + 1;
/* make sure the states and other variables are in their init state */
hal_port_reset_state(p);
/* read dot-config values to get the interface name */
sprintf(key,"PORT%02i_IFACE",port_i);
if( (retValue=libwr_cfg_get(key))==NULL)
return -1;
strncpy(p->name, retValue, 16);
strncpy(ps->name, retValue, 16);
/* check if the port is built into the firmware, if not, we are done */
if (!hal_port_check_presence(p->name, p->hw_addr))
if (!hal_port_check_presence(ps->name, ps->hw_addr))
return -1;
p->state = HAL_PORT_STATE_DISABLED;
p->in_use = 1;
p->calib.phy_tx_min = p->calib.phy_rx_min = 0; // No longer used
ps->in_use = 1;
ps->lpdc.isSupported = hal_port_check_lpdc_support(ps);
p->calib.delta_tx_board = 0; /* never set */
p->calib.delta_rx_board = 0; /* never set */
if ( ps->lpdc.isSupported ) {
// Allocate memory for tx/rx setup
ps->lpdc.txSetup = wrs_shm_alloc(hal_shmem_hdr,sizeof(halPortLpdcTx_t));
ps->lpdc.rxSetup = wrs_shm_alloc(hal_shmem_hdr,sizeof(halPortLpdcRx_t));
if ( ps->lpdc.rxSetup==NULL || ps->lpdc.txSetup==NULL) {
pr_error("Can't allocate LPDC (rx/tx) structures in shmem\n");
return -1;
}
}
/* get the number of a port from notation wriX */
sscanf(p->name + 3, "%d", &p->hw_index);
sscanf(ps->name + 3, "%d", &ps->hw_index);
/* hw_index is 0..17, p->name wri1..18 */
p->hw_index--;
p->t2_phase_transition = DEFAULT_T2_PHASE_TRANS;
p->t4_phase_transition = DEFAULT_T4_PHASE_TRANS;
p->clock_period = REF_CLOCK_PERIOD_PS;
ps->hw_index--;
/* Get fiber type */
p->fiber_index = 0; /* Default fiber value */
ps->fiber_index = 0; /* Default fiber value */
sprintf(key,"PORT%02i_FIBER",port_i);
if( (retValue=libwr_cfg_get(key))==NULL ) {
pr_error("port %i (%s): no key \"%s\" specified. Default fiber 0\n",
port_i, p->name,key);
port_i, ps->name,key);
} else {
if (sscanf(retValue, "%i", &p->fiber_index) != 1) {
if (sscanf(retValue, "%i", &ps->fiber_index) != 1) {
pr_error("port %i (%s): Invalid key \"%s\" value (%d). Default fiber 0\n",
port_i, p->name, key,*retValue);
port_i, ps->name, key,*retValue);
}
}
/* read dot-config values to get the number of defined fibers */
strcpy(key,"N_FIBER_ENTRIES");
if( (retValue=libwr_cfg_get(key))==NULL) {
pr_error("port %i (%s): no key \"%s\" specified\n",
port_i, p->name,key);
port_i, ps->name,key);
maxFibers=-1;
} else
if (sscanf(retValue, "%i", &maxFibers) != 1) {
......@@ -171,63 +179,57 @@ static int hal_port_init(int index)
maxFibers=-1;
}
if (p->fiber_index > maxFibers) {
if (ps->fiber_index > maxFibers) {
pr_error("port %i (%s): "
"not supported fiber value (%d), default to 0\n",
port_i, p->name,p->fiber_index);
p->fiber_index = 0;
port_i, ps->name,ps->fiber_index);
ps->fiber_index = 0;
}
/* Enable port monitoring by default */
p->monitor = HAL_PORT_MONITOR_ENABLE;
ps->monitor = HAL_PORT_MONITOR_ENABLE;
sprintf(key,"PORT%02i_INST%02i_MONITOR",port_i,1);
if ((retValue = libwr_cfg_get(key)) == NULL ) {
pr_error("port %i (%s): no key \"%s\" specified. Default to"
" monitor=y.\n",
port_i, p->name,key);
port_i, ps->name,key);
} else {
if (!strcasecmp(retValue, "n")) {
p->monitor = HAL_PORT_MONITOR_DISABLE;
ps->monitor = HAL_PORT_MONITOR_DISABLE;
pr_info("port %i (%s): monitor=n (%i)\n", port_i,
p->name, p->monitor);
ps->name, ps->monitor);
} else if (!strcasecmp(retValue, "y")) {
p->monitor = HAL_PORT_MONITOR_ENABLE;
ps->monitor = HAL_PORT_MONITOR_ENABLE;
pr_info("port %i (%s): monitor=y (%i)\n", port_i,
p->name, p->monitor);
ps->name, ps->monitor);
} else {
/* error */
pr_error("port %i (%s): not supported \"monitor\" "
"value, default to y\n",
port_i, p->name);
port_i, ps->name);
}
}
/* Used to pre-calibrate the TX path for each port. No more in V3 */
/* FIXME: this address should come from the driver header */
p->ep_base = 0x30000 + 0x400 * p->hw_index;
ps->ep_base = 0x30000 + 0x400 * ps->hw_index;
return 0;
}
/* Interates via all the ports defined in the config file and
* intializes them one after another. */
int hal_port_init_shmem(char *logfilename)
int hal_port_shmem_init(char *logfilename)
{
int index;
char *ret;
pr_info("Initializing switch ports...\n");
/* default timeouts */
libwr_tmo_init(&hal_port_tmo_sfp, UPDATE_SFP_PERIOD, 1);
libwr_tmo_init(&hal_port_tmo_rts, UPDATE_RTS_PERIOD, 1);
libwr_tmo_init(&update_sync_leds_tmo, UPDATE_SYNC_LEDS_PERIOD, 1);
libwr_tmo_init(&update_link_leds_tmo, UPDATE_LINK_LEDS_PERIOD, 1);
libwr_tmo_init(&update_sfp_dom_tmo, UPDATE_SFP_DOM_PERIOD, 1);
timer_init(_timerParameters,PORT_TIMER_COUNT);
/* Open a single raw socket for accessing the MAC addresses, etc. */
hal_port_fd = socket(AF_PACKET, SOCK_DGRAM, 0);
if (hal_port_fd < 0) {
halPorts.hal_port_fd = socket(AF_PACKET, SOCK_DGRAM, 0);
if (halPorts.hal_port_fd < 0) {
pr_error("Can't create socket: %s\n", strerror(errno));
return -1;
}
......@@ -240,28 +242,33 @@ int hal_port_init_shmem(char *logfilename)
return -1;
}
hal_shmem = wrs_shm_alloc(hal_shmem_hdr, sizeof(*hal_shmem));
ports = wrs_shm_alloc(hal_shmem_hdr,
hal_shmem->shmemState= HAL_SHMEM_STATE_NOT_INITITALIZED;
halPorts.ports = wrs_shm_alloc(hal_shmem_hdr,
sizeof(struct hal_port_state)
* HAL_MAX_PORTS);
if (!hal_shmem || !ports) {
if (!hal_shmem || !halPorts.ports) {
pr_error("Can't allocate in shmem\n");
return -1;
}
hal_shmem->ports = ports;
hal_shmem->ports = halPorts.ports;
for (index = 0; index < HAL_MAX_PORTS; index++)
if (hal_port_init(index) < 0)
if (hal_port_init(&halPorts.ports[index],index) < 0)
break;
hal_port_nports = index;
hal_port_state_fsm_init_all(halPorts.ports, &halPorts.globalLpdc); // Init main port FSM for all ports
led_init_all_ports(halPorts.ports); // Reset all leds
halPorts.numberOfPorts = index;
pr_info("Number of physical ports supported in HW: %d\n",
hal_port_nports);
halPorts.numberOfPorts );
/* We are done, mark things as valid */
hal_shmem->nports = hal_port_nports;
hal_shmem->hal_mode = hal_get_timing_mode();
hal_shmem->nports = halPorts.numberOfPorts ;
hal_shmem->hal_mode = hal_tmg_get_mode(NULL);
ret = libwr_cfg_get("READ_SFP_DIAG_ENABLE");
if (ret && !strcmp(ret, "y")) {
......@@ -270,6 +277,9 @@ int hal_port_init_shmem(char *logfilename)
} else
hal_shmem->read_sfp_diag = READ_SFP_DIAG_DISABLE;
hal_shmem->shmemState= HAL_SHMEM_STATE_INITITALIZING;
hal_shmem_hdr->version = HAL_SHMEM_VERSION;
/* Release processes waiting for HAL's to fill shm with correct data
When shm is opened successfully data in shm is still not populated!
......@@ -280,214 +290,122 @@ int hal_port_init_shmem(char *logfilename)
return 0;
}
int hal_port_init_wripc(char *logfilename)
{
/* Create a WRIPC server for HAL public API */
return hal_init_wripc(ports, logfilename);
}
/* Checks if the link is up on inteface (if_name). Returns non-zero if yes. */
static int hal_port_check_link(const char *if_name)
int pcs_writel(struct hal_port_state *ps, uint16_t value, int reg)
{
struct ifreq ifr;
uint32_t rv;
strncpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
strncpy(ifr.ifr_name, ps->name, sizeof(ifr.ifr_name));
if (ioctl(hal_port_fd, SIOCGIFFLAGS, &ifr) > 0)
rv = NIC_WRITE_PHY_CMD(reg, value);
ifr.ifr_data = (void *)&rv;
if (ioctl(halPorts.hal_port_fd, PRIV_IOCPHYREG, &ifr) < 0)
{
pr_error("%s: ioctl failed writing at register adress %d\n",
__func__,reg);
return -1;
};
return (ifr.ifr_flags & IFF_UP && ifr.ifr_flags & IFF_RUNNING);
}
/* Port locking state machine - controls the HPLL/DMPLL. TODO (v3):
get rid of this code - this will all be moved to the realtime CPU
inside the FPGA and the softpll. */
static void hal_port_locking_fsm(struct hal_port_state * p)
{
return 0;
}
int hal_port_pshifter_busy()
int pcs_readl(struct hal_port_state * p, int reg, uint32_t *value)
{
struct rts_pll_state *hs = &hal_port_rts_state;
if (!hal_port_rts_state_valid)
return 1;
struct ifreq ifr;
if (hs->current_ref != REF_NONE) {
int busy =
hs->channels[hs->current_ref].
flags & CHAN_SHIFTING ? 1 : 0;
strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name));
if (0)
pr_info("PSBusy %d, flags %x\n", busy,
hs->channels[hs->current_ref].flags);
return busy;
*value = NIC_READ_PHY_CMD(reg);
ifr.ifr_data = (void *)value;
if (ioctl(halPorts.hal_port_fd, PRIV_IOCPHYREG, &ifr) < 0) {
pr_error("%s: ioctl failed reading register at address %d\n",
__func__, reg);
return -1;
}
return 1;
}
/* Updates the current value of the phase shift on a given
* port. Called by the main update function regularly. */
int hal_port_poll_rts_state(void)
{
struct rts_pll_state *hs = &hal_port_rts_state;
hal_port_rts_state_valid = rts_get_state(hs) < 0 ? 0 : 1;
if (!hal_port_rts_state_valid)
printf("rts_get_state failure, weird...\n");
return hal_port_rts_state_valid;
*value=NIC_RESULT_DATA(*value);
return 0;
}
static uint32_t pcs_readl(struct hal_port_state * p, int reg)
static uint32_t ep_read(struct hal_port_state * p, int reg_addr, uint32_t *value)
{
struct ifreq ifr;
uint32_t rv;
strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name));
rv = NIC_READ_PHY_CMD(reg);
ifr.ifr_data = (void *)&rv;
// printf("raw fd %d name %s\n", hal_port_fd, ifr.ifr_name);
if (ioctl(hal_port_fd, PRIV_IOCPHYREG, &ifr) < 0) {
pr_error("ioctl failed\n");
};
// printf("PCS_readl: reg %d data %x\n", reg, NIC_RESULT_DATA(rv));
return NIC_RESULT_DATA(rv);
}
static int hal_port_link_down(struct hal_port_state * p, int link_up)
{
/* If, at any moment, the link goes down, reset the FSM and
* the port state structure. */
if (!link_up && p->state != HAL_PORT_STATE_LINK_DOWN
&& p->state != HAL_PORT_STATE_DISABLED) {
if (p->locked) {
pr_info("Switching RTS to use local reference\n");
if (hal_get_timing_mode()
!= HAL_TIMING_MODE_GRAND_MASTER) {
shw_pps_set_timing_mode(HAL_TIMING_MODE_FREE_MASTER);
hal_update_timing_mode();
}
}
/* turn off synced LED */
set_led_synced(p->hw_index, 0);
/* turn off link/wrmode LEDs */
set_led_wrmode(p->hw_index, SFP_LED_WRMODE_OFF);
p->state = HAL_PORT_STATE_LINK_DOWN;
hal_port_reset_state(p);
rts_enable_ptracker(p->hw_index, 0);
pr_info("%s: link down\n", p->name);
return 1;
*value = reg_addr;
ifr.ifr_data = (void *) value;
pr_info("raw fd %d name %s\n", halPorts.hal_port_fd, ifr.ifr_name);
if (ioctl(halPorts.hal_port_fd, PRIV_IOCREADREG, &ifr) < 0) {
pr_error("%s: ioctl failed reading register at address %d\n", __func__,reg_addr);
return -1;
}
pr_info("ep_read: reg %d data %x\n", reg_addr, *value);
return 0;
}
/* Main port state machine */
static void hal_port_fsm(struct hal_port_state * p)
/* checks if the port supports Low Phase Drift Calibration*/
static int hal_port_check_lpdc_support(struct hal_port_state * ps)
{
struct rts_pll_state *hs = &hal_port_rts_state;
int link_up = hal_port_check_link(p->name);
if (hal_port_link_down(p, link_up))
return;
/* handle the locking part */
hal_port_locking_fsm(p);
switch (p->state) {
case HAL_PORT_STATE_DISABLED:
p->calib.tx_calibrated = 0;
p->calib.rx_calibrated = 0;
break;
/* Default state - wait until the link goes up */
case HAL_PORT_STATE_LINK_DOWN:
case HAL_PORT_STATE_RESET:
{
if (link_up) {
uint32_t bit_slide_steps;
p->calib.tx_calibrated = 1;
p->calib.rx_calibrated = 1;
/* FIXME: use proper register names */
bit_slide_steps=(pcs_readl(p, 16) >> 4) & 0x1f;
p->calib.bitslide_ps=bit_slide_steps*800; /* 1 step = 800ps */
pr_info("Bitslide: %d\n",bit_slide_steps);
p->calib.delta_rx_phy =
p->calib.phy_rx_min;
p->calib.delta_tx_phy = p->calib.phy_tx_min;
if (0)
pr_info(
"Bypassing calibration for "
"downlink port %s [dTx %d, dRx %d]\n",
p->name, p->calib.delta_tx_phy,
p->calib.delta_rx_phy);
p->tx_cal_pending = 0;
p->rx_cal_pending = 0;
/* Set link/wrmode LEDs to other. Master/slave
* color is set in the different place */
set_led_wrmode(p->hw_index,
SFP_LED_WRMODE_OTHER);
pr_info("%s: link up\n", p->name);
p->state = HAL_PORT_STATE_UP;
}
break;
}
uint32_t rv;
/* Default "on" state - just keep polling the phase value. */
case HAL_PORT_STATE_UP:
if (hal_port_rts_state_valid) {
p->phase_val =
hs->channels[p->hw_index].phase_loopback;
p->phase_val_valid =
hs->channels[p->hw_index].
flags & CHAN_PMEAS_READY ? 1 : 0;
//hal_port_check_lock(p->name);
//p->locked =
if ( ep_read(ps, EP_ECR_ADDR,&rv)<0 ) {
pr_error("Cannot detects supports for Low Phase Drift Calibration "
"at port %s\n", ps->name);
return 0;
} else {
if (rv & EP_ECR_FEAT_LPC) {
pr_info("Supports for Low Phase Drift Calibration detected "
"at port %s\n", ps->name);
return 1;
} else {
pr_info("NO supports for Low Phase Drift Calibration detected "
"at port %s\n", ps->name);
return 0;
}
}
}
break;
int hal_port_wripc_init(char *logfilename)
{
/* Create a WRIPC server for HAL public API */
return hal_wripc_init(halPorts.ports, logfilename);
}
/* Locking state (entered on calling hal_port_start_lock()). */
case HAL_PORT_STATE_LOCKING:
/* Once the locking FSM is done, go back to the "UP" state. */
int hal_port_pshifter_busy()
{
struct rts_pll_state *hs = getRtsStatePtr();
p->locked = hal_port_check_lock(p->name);
if (! isRtsStateValid() )
return 1;
if (p->locked) {
pr_info("[main-fsm] Port %s locked.\n",
p->name);
p->state = HAL_PORT_STATE_UP;
}
if (hs->current_ref != REF_NONE) {
int busy =
hs->channels[hs->current_ref].
flags & CHAN_SHIFTING ? 1 : 0;
break;
return busy;
}
/* Calibration state (entered by starting the
* calibration with halexp_calibration_cmd()) */
case HAL_PORT_STATE_CALIBRATION:
return 1;
}
/* Calibration still pending - if not anymore, go back
* to the "UP" state */
if (p->rx_cal_pending || p->tx_cal_pending) {
} //calibration_fsm(p);
else
p->state = HAL_PORT_STATE_UP;
/* Updates the current value of the phase shift on a given
* port. Called by the main update function regularly. */
int hal_port_poll_rts_state(void)
{
struct rts_pll_state *hs = getRtsStatePtr();
break;
}
setRtsStateValidity( rts_get_state(hs) < 0 ? 0 : 1);
if (! isRtsStateValid() )
pr_warning("rts_get_state failure, weird...\n");
return isRtsStateValid();
}
static void hal_port_insert_sfp(struct hal_port_state * p)
static void hal_port_insert_sfp(struct hal_port_state * ps)
{
struct shw_sfp_header shdr;
struct shw_sfp_caldata *cdata;
......@@ -495,72 +413,75 @@ static void hal_port_insert_sfp(struct hal_port_state * p)
int err;
memset(&shdr, 0, sizeof(struct shw_sfp_header));
memset(&p->calib.sfp_dom_raw, 0, sizeof(struct shw_sfp_dom));
err = shw_sfp_read_verify_header(p->hw_index, &shdr);
memcpy(&p->calib.sfp_header_raw, &shdr, sizeof(struct shw_sfp_header));
memset(&ps->calib.sfp_dom_raw, 0, sizeof(struct shw_sfp_dom));
err = shw_sfp_read_verify_header(ps->hw_index, &shdr);
if (err == -2) {
pr_error("%s SFP module not inserted. Failed to read SFP "
"configuration header\n", p->name);
"configuration header\n", ps->name);
return;
} else if (err < 0) {
pr_error("Failed to read SFP configuration header for %s\n",
p->name);
return;
ps->name);
/* Clear any mess that was wrongly read to make sure that
the SFP will be unrecognized. */
memset(&shdr, 0, sizeof(struct shw_sfp_header));
}
memcpy(&ps->calib.sfp_header_raw, &shdr, sizeof(struct shw_sfp_header));
if (hal_shmem->read_sfp_diag == READ_SFP_DIAG_ENABLE
&& shdr.diagnostic_monitoring_type & SFP_DIAGNOSTIC_IMPLEMENTED) {
pr_info("SFP Diagnostic Monitoring implemented in SFP plugged"
" to port %d (%s)\n", p->hw_index + 1, p->name);
" to port %d (%s)\n", ps->hw_index + 1, ps->name);
if (shdr.diagnostic_monitoring_type & SFP_ADDR_CHANGE_REQ) {
pr_warning("SFP in port %d (%s) requires special "
"address change before accessing Diagnostic"
" Monitoring, which is not implemented "
"right now\n", p->hw_index + 1, p->name);
"right now\n", ps->hw_index + 1, ps->name);
} else {
/* copy coontent of SFP's Diagnostic Monitoring */
shw_sfp_read_dom(p->hw_index, &p->calib.sfp_dom_raw);
shw_sfp_read_dom(ps->hw_index, &ps->calib.sfp_dom_raw);
if (err < 0) {
pr_error("Failed to read SFP Diagnostic "
"Monitoring for port %d (%s)\n",
p->hw_index + 1, p->name);
ps->hw_index + 1, ps->name);
}
p->has_sfp_diag = 1;
ps->has_sfp_diag = 1;
}
}
pr_info("SFP Info: Manufacturer: %.16s P/N: %.16s, S/N: %.16s\n",
shdr.vendor_name, shdr.vendor_pn, shdr.vendor_serial);
cdata = shw_sfp_get_cal_data(p->hw_index, &shdr);
cdata = shw_sfp_get_cal_data(ps->hw_index, &shdr);
if (cdata) {
/* Alpha is not known now. It is read later from the fibers'
* database. */
pr_info("%s SFP Info: (%s) delta Tx %d, delta Rx %d, "
"TX wl: %dnm, RX wl: %dnm\n", p->name,
"TX wl: %dnm, RX wl: %dnm\n", ps->name,
cdata->flags & SFP_FLAG_CLASS_DATA
? "class-specific" : "device-specific",
cdata->delta_tx_ps, cdata->delta_rx_ps, cdata->tx_wl,
cdata->rx_wl);
memcpy(&p->calib.sfp, cdata,
memcpy(&ps->calib.sfp, cdata,
sizeof(struct shw_sfp_caldata));
/* Mark SFP as found in data base */
p->calib.sfp.flags |= SFP_FLAG_IN_DB;
ps->calib.sfp.flags |= SFP_FLAG_IN_DB;
} else {
pr_error("Unknown SFP vn=\"%.16s\" pn=\"%.16s\" "
"vs=\"%.16s\" on port %s\n", shdr.vendor_name,
shdr.vendor_pn, shdr.vendor_serial, p->name);
memset(&p->calib.sfp, 0, sizeof(p->calib.sfp));
shdr.vendor_pn, shdr.vendor_serial, ps->name);
memset(&ps->calib.sfp, 0, sizeof(ps->calib.sfp));
}
p->state = HAL_PORT_STATE_LINK_DOWN;
shw_sfp_set_tx_disable(p->hw_index, 0);
ps->sfpPresent=1;
shw_sfp_set_tx_disable(ps->hw_index, 0);
/* Copy the strings anyways, for informative value in shmem */
strncpy(p->calib.sfp.vendor_name, (void *)shdr.vendor_name, 16);
strncpy(p->calib.sfp.part_num, (void *)shdr.vendor_pn, 16);
strncpy(p->calib.sfp.vendor_serial, (void *)shdr.vendor_serial, 16);
strncpy(ps->calib.sfp.vendor_name, (void *)shdr.vendor_name, 16);
strncpy(ps->calib.sfp.part_num, (void *)shdr.vendor_pn, 16);
strncpy(ps->calib.sfp.vendor_serial, (void *)shdr.vendor_serial, 16);
/* check if SFP is 1GbE */
p->calib.sfp.flags |= shdr.br_nom == SFP_SPEED_1Gb ? SFP_FLAG_1GbE : 0;
p->calib.sfp.flags |= shdr.br_nom == SFP_SPEED_1Gb_10 ? SFP_FLAG_1GbE : 0;
ps->calib.sfp.flags |= shdr.br_nom == SFP_SPEED_1Gb ? SFP_FLAG_1GbE : 0;
ps->calib.sfp.flags |= shdr.br_nom == SFP_SPEED_1Gb_10 ? SFP_FLAG_1GbE : 0;
/*
* Now, we should fix the alpha value according to fiber
......@@ -568,357 +489,318 @@ static void hal_port_insert_sfp(struct hal_port_state * p)
* speed ratio of the SFP frequencies over the specific
* fiber. Thus, rely on the fiber type for this port.
*/
sprintf(subname, "alpha_%i_%i", p->calib.sfp.tx_wl, p->calib.sfp.rx_wl);
sprintf(subname, "alpha_%i_%i", ps->calib.sfp.tx_wl, ps->calib.sfp.rx_wl);
err = libwr_cfg_convert2("FIBER%02i_PARAMS", subname,
LIBWR_DOUBLE, &p->calib.sfp.alpha,
p->fiber_index);
LIBWR_DOUBLE, &ps->calib.sfp.alpha,
ps->fiber_index);
if (!err) {
/* Now we know alpha, so print it. */
pr_info("%s SFP Info: alpha %.3f (* 1e6) found for TX wl: %dnm,"
" RX wl: %dmn\n", p->name, p->calib.sfp.alpha * 1e6,
p->calib.sfp.tx_wl, p->calib.sfp.rx_wl);
" RX wl: %dmn\n", ps->name, ps->calib.sfp.alpha * 1e6,
ps->calib.sfp.tx_wl, ps->calib.sfp.rx_wl);
return;
}
/* Try again, with the opposite direction (rx/tx) */
sprintf(subname, "alpha_%i_%i", p->calib.sfp.rx_wl, p->calib.sfp.tx_wl);
sprintf(subname, "alpha_%i_%i", ps->calib.sfp.rx_wl, ps->calib.sfp.tx_wl);
err = libwr_cfg_convert2("FIBER%02i_PARAMS", subname,
LIBWR_DOUBLE, &p->calib.sfp.alpha,
p->fiber_index);
LIBWR_DOUBLE, &ps->calib.sfp.alpha,
ps->fiber_index);
if (!err) {
p->calib.sfp.alpha = (1.0 / (1.0 + p->calib.sfp.alpha)) - 1.0;
ps->calib.sfp.alpha = (1.0 / (1.0 + ps->calib.sfp.alpha)) - 1.0;
/* Now we know alpha, so print it. */
pr_info("%s SFP Info: alpha %.3f (* 1e6) found for TX wl: %dnm,"
" RX wl: %dmn\n", p->name, p->calib.sfp.alpha * 1e6,
p->calib.sfp.tx_wl, p->calib.sfp.rx_wl);
" RX wl: %dmn\n", ps->name, ps->calib.sfp.alpha * 1e6,
ps->calib.sfp.tx_wl, ps->calib.sfp.rx_wl);
return;
}
pr_error("Port %s, SFP vn=\"%.16s\" pn=\"%.16s\" vs=\"%.16s\", "
"fiber %i: no alpha known\n", p->name,
p->calib.sfp.vendor_name, p->calib.sfp.part_num,
p->calib.sfp.vendor_serial, p->fiber_index);
p->calib.sfp.alpha = 0;
"fiber %i: no alpha known\n", ps->name,
ps->calib.sfp.vendor_name, ps->calib.sfp.part_num,
ps->calib.sfp.vendor_serial, ps->fiber_index);
ps->calib.sfp.alpha = 0;
}
static void hal_port_remove_sfp(struct hal_port_state * p)
static void hal_port_remove_sfp(struct hal_port_state * ps)
{
hal_port_link_down(p, 0);
p->state = HAL_PORT_STATE_DISABLED;
/* clean SFP's details when removing SFP */
memset(&p->calib.sfp, 0, sizeof(p->calib.sfp));
memset(&p->calib.sfp_header_raw, 0, sizeof(struct shw_sfp_header));
memset(&p->calib.sfp_dom_raw, 0, sizeof(struct shw_sfp_dom));
p->has_sfp_diag = 0;
memset(&ps->calib.sfp, 0, sizeof(ps->calib.sfp));
memset(&ps->calib.sfp_header_raw, 0, sizeof(struct shw_sfp_header));
memset(&ps->calib.sfp_dom_raw, 0, sizeof(struct shw_sfp_dom));
ps->has_sfp_diag=ps->sfpPresent=0;
}
/* detects insertion/removal of SFP transceivers */
static void hal_port_poll_sfp(void)
{
uint32_t mask = shw_sfp_module_scan();
static int old_mask = 0;
if (mask != old_mask) {
int i, hw_index;
for (i = 0; i < HAL_MAX_PORTS; i++) {
hw_index = ports[i].hw_index;
if (ports[i].in_use
&& (mask ^ old_mask) & (1 << hw_index)) {
int insert = mask & (1 << hw_index);
pr_info("SFP Info: Detected SFP %s "
"on port %s.\n",
insert ? "insertion" : "removal",
ports[i].name);
if (insert)
hal_port_insert_sfp(&ports[i]);
else
hal_port_remove_sfp(&ports[i]);
}
static int __old_mask = 0;
uint32_t mask = shw_sfp_module_scan();
if (mask != __old_mask) {
int i, hw_index;
/* lock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
for (i = 0; i < HAL_MAX_PORTS; i++) {
hw_index = halPorts.ports[i].hw_index;
if (halPorts.ports[i].in_use
&& (mask ^ __old_mask) & (1 << hw_index)) {
int insert = mask & (1 << hw_index);
pr_info("SFP Info: Detected SFP %s "
"on port %s.\n",
insert ? "insertion" : "removal",
halPorts.ports[i].name);
if (insert)
hal_port_insert_sfp(&halPorts.ports[i]);
else
hal_port_remove_sfp(&halPorts.ports[i]);
}
}
old_mask = mask;
}
/* Executes the port FSM for all ports. Called regularly by the main loop. */
void hal_port_update_all()
{
int i;
struct shw_sfp_dom sfp_dom_raw[HAL_MAX_PORTS];
/* unlock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
__old_mask = mask;
}
}
static void _cb_port_poll_rts_state(int timerId){
/* poll_rts_state does not write to shmem */
if (libwr_tmo_expired(&hal_port_tmo_rts))
hal_port_poll_rts_state();
/* lock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
if (libwr_tmo_expired(&hal_port_tmo_sfp))
hal_port_poll_sfp();
hal_port_poll_rts_state();
// Update timing mode
hal_shmem->hal_mode = hal_tmg_get_mode(NULL);
}
for (i = 0; i < HAL_MAX_PORTS; i++)
if (ports[i].in_use) {
hal_port_fsm(&ports[i]);
}
static void _cb_port_poll_sfp(int timerId){
hal_port_poll_sfp();
}
/* unlock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
static void _cb_port_poll_sfp_dom(int timerId){
if (hal_shmem->read_sfp_diag == READ_SFP_DIAG_ENABLE) {
struct shw_sfp_dom sfp_dom_raw[HAL_MAX_PORTS];
struct hal_port_state *ps;
int i;
if (hal_shmem->read_sfp_diag == READ_SFP_DIAG_ENABLE
&& libwr_tmo_expired(&update_sfp_dom_tmo)) {
/* get the DOM data to local memory */
ps=halPorts.ports;
for (i = 0; i < HAL_MAX_PORTS; i++) {
/* read DOM only for plugged ports with DOM
* capabilities */
if (ports[i].in_use
&& ports[i].state != HAL_PORT_STATE_DISABLED
&& (ports[i].has_sfp_diag)) {
shw_sfp_update_dom(ports[i].hw_index,
if (ps->in_use
&& ps->sfpPresent
&& ps->has_sfp_diag) {
shw_sfp_update_dom(ps->hw_index,
&sfp_dom_raw[i]);
}
ps++;
}
/* lock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
/* copy the DOM from local memory to shmem */
ps=halPorts.ports;
for (i = 0; i < HAL_MAX_PORTS; i++) {
/* update DOM only for plugged ports with DOM
* capabilities */
if (ports[i].in_use
&& ports[i].state != HAL_PORT_STATE_DISABLED
&& (ports[i].has_sfp_diag)) {
memcpy(&ports[i].calib.sfp_dom_raw,
if (ps->in_use
&& ps->sfpPresent
&& ps->has_sfp_diag) {
memcpy(&halPorts.ports[i].calib.sfp_dom_raw,
&sfp_dom_raw[i],
sizeof(struct shw_sfp_dom));
}
ps++;
}
/* unlock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
}
}
static void _cb_port_update_sync_leds(int timerId){
/* update LEDs of synced ports */
led_synched_update(halPorts.ports);
}
if (libwr_tmo_expired(&update_link_leds_tmo)) {
/* update color of the link LEDs */
update_link_leds();
static void _cb_port_update_link_leds(int timerId){
/* update color of the link LEDs */
led_link_update(halPorts.ports);
}
static void printFsmDebugInfo(void) {
static timeout_t _fsm_debug_tmo={.repeat=-1}; // Use -1 to know that the timer must be initialized
if ( _fsm_debug_tmo.repeat==-1) {
libwr_tmo_init( &_fsm_debug_tmo, 1000, 1 );
return;
}
if ( libwr_tmo_expired( &_fsm_debug_tmo ) ) {
int i;
printf("PortDBG: \n");
if (libwr_tmo_expired(&update_sync_leds_tmo)) {
/* update LEDs of synced ports */
update_sync_leds();
for(i = 0; i < HAL_MAX_PORTS; i++)
{
struct hal_port_state *ps = &halPorts.ports[i];
char txEventsStr[128];
char rxEventsStr[128];
char pllEventsStr[128];
char eventsStr[128];
if ( !ps->in_use )
continue;
strncpy( eventsStr, fsm_get_event_mask_as_string( &ps->fsm ), sizeof(txEventsStr) );
strncpy( txEventsStr, fsm_get_event_mask_as_string( &ps->lpdc.txSetupFSM ), sizeof(txEventsStr) );
strncpy( rxEventsStr, fsm_get_event_mask_as_string( &ps->lpdc.rxSetupFSM ), sizeof(rxEventsStr) );
strncpy( pllEventsStr, fsm_get_event_mask_as_string( &ps->pllFsm ), sizeof(rxEventsStr) );
printf(" wri%02d: PORT:%-12s[%-36s] TX:%-16s[%-10s] RX:%-12s[%-25s] PLL:%-12s[%-25s] bs=%d\n",
ps->hw_index + 1,
fsm_get_state_name( &ps->fsm ),
eventsStr,
fsm_get_state_name( &ps->lpdc.txSetupFSM ),
txEventsStr,
fsm_get_state_name( &ps->lpdc.rxSetupFSM ),
rxEventsStr,
fsm_get_state_name( &ps->pllFsm ),
pllEventsStr,
ps->calib.bitslide_ps
);
}
}
}
/* Executes the port FSM for all ports. Called regularly by the main loop. */
void hal_port_update_all()
{
timer_scan(_timerParameters,PORT_TIMER_COUNT);
/* lock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
hal_port_state_fsm_run_all(halPorts.ports);
/* unlock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
if ( FSM_DEBUG )
printFsmDebugInfo();
}
int hal_port_enable_tracking(const char *port_name)
{
const struct hal_port_state *p = hal_lookup_port(ports,
hal_port_nports, port_name);
const struct hal_port_state *ps = hal_lookup_port(halPorts.ports,
halPorts.numberOfPorts, port_name);
if (!p)
if (!ps)
return -1;
return rts_enable_ptracker(p->hw_index, 1); /* 0 or -1 already */
return rts_enable_ptracker(ps->hw_index, 1); /* 0 or -1 already */
}
/* Triggers the locking state machine, called by the PTPd during the
* WR link setup phase. */
int hal_port_start_lock(const char *port_name, int priority)
{
struct hal_port_state *p = hal_lookup_port(ports, hal_port_nports,
port_name);
int ret=-1;
if (!p && p->state != HAL_PORT_STATE_UP )
return -1; /* can't lock to a disconnected port */
struct hal_port_state *ps = hal_lookup_port(halPorts.ports, halPorts.numberOfPorts, port_name);
pr_info("Locking to port: %s\n", port_name);
if ( !ps )
return -1; /* unknown port */
hal_port_poll_rts_state(); // update rts state
if ( (hal_get_timing_mode()==HAL_TIMING_MODE_BC) &&
(ret=rts_lock_channel(p->hw_index, 0))==0 ) {
/* lock shmem */
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_BEGIN);
/* fixme: check the main FSM state before */
p->state = HAL_PORT_STATE_LOCKING;
wrs_shm_write(hal_shmem_hdr, WRS_SHM_WRITE_END);
if ( rts_lock_channel(ps->hw_index, 0)<0 ) {
return -1;
}
return ret;
ps->evt_lock=1;
return 0;
}
/* Returns 1 if the port is locked */
int hal_port_check_lock(const char *port_name)
/* Returns 1 if the port is locked, 0 if unlocked, -1 in case of error */
int hal_port_check_lock(const struct hal_port_state *ps)
{
const struct hal_port_state *p = hal_lookup_port(ports,
hal_port_nports, port_name);
struct rts_pll_state *hs = &hal_port_rts_state;
struct rts_pll_state *hs = getRtsStatePtr();
if (!p)
return 0; /* was -1, but it would confuse the caller */
if (!ps)
return -1;
if (!hal_port_rts_state_valid)
return 0;
if (!isRtsStateValid())
return -1;
if (hs->delock_count > 0)
return 0;
return -1;
return ( hs->mode==RTS_MODE_BC &&
hs->current_ref == p->hw_index &&
hs->current_ref == ps->hw_index &&
(hs->flags & RTS_DMTD_LOCKED) &&
(hs->flags & RTS_REF_LOCKED));
}
int hal_port_reset(const char *port_name)
{
struct hal_port_state *p = hal_lookup_port(ports,
hal_port_nports, port_name);
if (!p)
return -1;
if (p->state != HAL_PORT_STATE_LINK_DOWN
&& p->state != HAL_PORT_STATE_DISABLED) {
/* turn off synced LED */
set_led_synced(p->hw_index, 0);
/* turn off link/wrmode LEDs */
set_led_wrmode(p->hw_index, SFP_LED_WRMODE_OFF);
hal_port_reset_state(p);
p->state = HAL_PORT_STATE_RESET;
rts_enable_ptracker(p->hw_index, 0);
pr_info("%s: link down\n", p->name);
return 1;
}
return 0;
}
/* to avoid i2c transfers to set the link LEDs, cache their state */
static void set_led_wrmode(int p_index, int val)
/* Returns 1 if the port is locked */
int hal_port_check_lock_by_name(const char *port_name)
{
/* We assume that after the HAL is started all LEDs are off */
static int leds_map[HAL_MAX_PORTS];
if (p_index >= HAL_MAX_PORTS)
return;
const struct hal_port_state *ps = hal_lookup_port(halPorts.ports,
halPorts.numberOfPorts, port_name);
if (leds_map[p_index] == val) {
/* value has not changed */
return;
}
/* update the LED, don't forget to turn off LEDs if needed */
if (val == SFP_LED_WRMODE_SLAVE) {
/* cannot set and clear LED in the same call! */
shw_sfp_set_generic(p_index, 1, SFP_LED_WRMODE1);
shw_sfp_set_generic(p_index, 0, SFP_LED_WRMODE2);
} else if (val == SFP_LED_WRMODE_OTHER) {
/* cannot set and clear LED in the same call! */
shw_sfp_set_generic(p_index, 0, SFP_LED_WRMODE1);
shw_sfp_set_generic(p_index, 1, SFP_LED_WRMODE2);
} else if (val == SFP_LED_WRMODE_MASTER) {
shw_sfp_set_generic(p_index, 1,
SFP_LED_WRMODE1 | SFP_LED_WRMODE2);
} else if (val == SFP_LED_WRMODE_OFF) {
shw_sfp_set_generic(p_index, 0,
SFP_LED_WRMODE1 | SFP_LED_WRMODE2);
}
leds_map[p_index] = val;
return hal_port_check_lock(ps);
}
static void update_link_leds(void) {
int i;
struct hal_port_state *port = &ports[0];
for (i = 0; i < HAL_MAX_PORTS; i++) {
if (port->in_use && state_up(port->state)) {
if (port->portMode == PORT_MODE_SLAVE)
set_led_wrmode(i, SFP_LED_WRMODE_SLAVE);
else if (port->portMode == PORT_MODE_MASTER)
set_led_wrmode(i, SFP_LED_WRMODE_MASTER);
else
set_led_wrmode(i, SFP_LED_WRMODE_OTHER);
}
port++;
}
}
/* to avoid i2c transfers to set the synced LEDs, cache their state */
static void set_led_synced(int p_index, int val)
int hal_port_reset(const char *port_name)
{
/* We assume that after the HAL is started all LEDs are off */
static int leds_map[HAL_MAX_PORTS];
struct hal_port_state *ps = hal_lookup_port(halPorts.ports,
halPorts.numberOfPorts, port_name);
if (p_index >= HAL_MAX_PORTS)
return;
if (leds_map[p_index] == val) {
/* value has not changed */
return;
}
if (!ps)
return -1;
/* update the LED */
shw_sfp_set_led_synced(p_index, val);
leds_map[p_index] = val;
ps->evt_reset=1;
return 0;
}
static void update_sync_leds(void)
{
// Check if all ports are initialized
int hal_port_all_ports_initialized(void) {
struct hal_port_state *ps=halPorts.ports;
int i;
struct hal_port_state *port=&ports[0];
for (i = 0; i < HAL_MAX_PORTS; i++) {
/* Check:
* --port in use
* --link is up
*/
if ( port->in_use
&& state_up(port->state) ) {
int ledValue;
/* Check:
* --ppsi instance in slave state
* --servo is locked
* --WR of HA PTP servo
* --servo is updating
*/
ledValue= port->synchronized
&& (port->portInfoUpdated--) > -10
? 1 : 0;
set_led_synced(i, ledValue);
if ( ps->in_use && fsm_get_state( &ps->fsm ) ==HAL_PORT_STATE_INIT ) {
// This port has not finished its initialization state
return 0;
}
port++;
ps++;
}
return 1;
}
void hal_update_port_info(char *iface_name, int mode, int synchronized){
void hal_port_update_info(char *iface_name, int mode, int synchronized){
int i;
struct hal_port_state *port=&ports[0];
struct hal_port_state *ps=halPorts.ports;
if ( iface_name==NULL ) {
pr_error("%s: Invalid iface_name parameter (NULL).\n",__func__);
return;
}
/* TODO: Improve/implement handling of many instances on a single
physical port. Currently, the PPSi instance with the greatest
index will override any preceeding instances on a give physical
port. Very likely, a table with counter of instances per physical
port could be done. If the counter is greater than 1, the arbiration
kicks in. In such case, we could follow what was implemente
previously, namely an Instance in PTP SLAVE state has precedence.
TO BE DISCUSSED.
*/
for (i = 0; i < HAL_MAX_PORTS; i++) {
if (port->in_use &&
state_up(port->state) &&
!strcmp(iface_name,port->name) ) {
if (ps->in_use &&
!strcmp(iface_name,ps->name) ) {
port->portMode=mode;
port->synchronized=synchronized;
port->portInfoUpdated=1;
ps->portMode=mode;
ps->synchronized=synchronized;
ps->portInfoUpdated=1;
break;
}
port++;
ps++;
}
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_PORTS_H
#define HAL_PORTS_H
#include <rt_ipc.h>
#include <libwr/hal_shmem.h>
typedef struct {
struct hal_port_state *ports;
int numberOfPorts;
int hal_port_fd; /* An fd of always opened raw sockets for ioctl()-ing Ethernet devices */
/* RT subsystem PLL state, polled regularly via mini-ipc */
struct rts_pll_state rts_state;
int rts_state_valid;
/* Global information needed by LPDC (tx setup) */
halGlobalLPDC_t globalLpdc;
}hal_ports_t;
typedef void (*hal_cleanup_callback_t)(void);
#define isRtsStateValid() halPorts.rts_state_valid
#define setRtsStateValidity(value) halPorts.rts_state_valid=(value)
#define getRtsState() (halPorts.rts_state)
#define getRtsStatePtr() (&getRtsState())
extern hal_ports_t halPorts;
extern int hal_port_poll_rts_state(void);
extern int hal_port_start_lock(const char *port_name, int priority);
extern int hal_port_enable_tracking(const char *port_name);
extern int hal_port_check_lock(const struct hal_port_state *ps);
extern int hal_port_check_lock_by_name(const char *port_name);
extern int hal_port_all_ports_initialized(void);
extern int hal_port_reset(const char *port_name);
extern int hal_port_pshifter_busy(void);
extern void hal_port_update_info(char *iface_name, int mode, int synchronized);
extern void hal_port_update_all(void);
extern int hal_port_shmem_init(char *logfilename);
extern int hal_port_wripc_init(char *logfilename);
extern int rts_lock_channel(int channel, int priority);
extern int hal_wripc_init(struct hal_port_state *hal_ports, char *logfilename);
extern int hal_wripc_update(int ms_timeout);
extern int hal_check_running(void);
extern int hal_add_cleanup_callback(hal_cleanup_callback_t cb);
extern int pcs_writel(struct hal_port_state *p, uint16_t value, int reg);
extern int pcs_readl(struct hal_port_state * p, int reg, uint32_t *value);
#endif
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*/
#include "hal_timer.h"
void timer_init(timer_parameter_t *p,int nbTimers) {
int index;
for ( index=0; index < nbTimers; index++ ) {
libwr_tmo_init(&p->timer,p->tmoMs, p->repeat);
p++;
}
}
void timer_scan(timer_parameter_t *p,int nbTimers) {
int index;
for ( index=0; index < nbTimers; index++ ) {
if (libwr_tmo_expired(&p->timer) && p->cb!=NULL) {
(*p->cb)(p->id);
}
p++;
}
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*/
#ifndef HAL_TIMER_H
#define HAL_TIMER_H
#include <libwr/timeout.h>
typedef struct {
int id;
uint32_t tmoMs; // Time out value in ms
int repeat; // restart timeout automatically
timeout_t timer;
void (*cb)(int timerId);
}timer_parameter_t;
extern void timer_init(timer_parameter_t *p,int nbTimers);
extern void timer_scan(timer_parameter_t *p,int nbTimers);
#endif
......@@ -11,33 +11,35 @@
#include <libwr/wrs-msg.h>
#include <libwr/timeout.h>
#include "wrsw_hal.h"
#include <rt_ipc.h>
#include <hal_exports.h>
#include "hal_exports.h"
#include "hal_ports.h"
extern struct rts_pll_state hal_port_rts_state;
extern int hal_port_rts_state_valid;
int hal_init_timing_mode(void)
int hal_tmg_init(const char * logfilename)
{
if (rts_connect(NULL) < 0) {
pr_error(
"Failed to establish communication with the RT subsystem.\n");
return -1;
}
return 0;
}
int hal_init_timing(char *filename)
{
if( rts_set_mode( RTS_MODE_GM_FREERUNNING ) < 0 )
{
pr_error(
"Failed to configure PLL in free-running master mode.\n");
return -1;
}
return 0;
}
int hal_get_timing_mode(void)
int hal_tmg_get_mode(uint32_t *hwIndex)
{
struct rts_pll_state *hs = &hal_port_rts_state;
struct rts_pll_state *hs = getRtsStatePtr();
if (hal_port_rts_state_valid)
if (isRtsStateValid()) {
if ( hwIndex!=NULL )
*hwIndex=hs->current_ref;
switch (hs->mode) {
case RTS_MODE_GM_EXTERNAL:
return HAL_TIMING_MODE_GRAND_MASTER;
......@@ -48,11 +50,14 @@ int hal_get_timing_mode(void)
case RTS_MODE_DISABLED:
return HAL_TIMING_MODE_DISABLED;
}
}
return -1;
}
int hal_update_timing_mode(void) {
return hal_port_poll_rts_state();
int hal_tmg_set_mode(uint32_t tm) {
int ret=shw_pps_set_timing_mode(tm);
hal_port_poll_rts_state();
return ret;
}
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Jean-Claude BAU - CERN
*
* Released according to the GNU LGPL, version 2.1 or any later version.
*
*
*/
#ifndef HAL_TIMING_H
#define HAL_TIMING_H
extern int hal_tmg_set_mode(uint32_t tm);
extern int hal_tmg_get_mode(uint32_t *hwIndex);
extern int hal_tmg_init(const char * logfilename);
#endif
#ifndef __WRSW_HAL_H
#define __WRSW_HAL_H
#include <inttypes.h>
typedef void (*hal_cleanup_callback_t)(void);
int hal_check_running(void);
int hal_parse_config(void);
void hal_config_set_config_file(const char *str);
int hal_config_extra_cmdline(const char *str);
int hal_config_get_int(const char *name, int *value);
int hal_config_get_double(const char *name, double *value);
int hal_config_get_string(const char *name, char *value, int max_len);
int hal_config_iterate(const char *section, int index,
char *subsection, int max_len);
int hal_port_init_shmem(char *logfilename);
int hal_port_init_wripc(char *logfilename);
void hal_port_update_all(void);
void hal_update_port_info(char *iface_name, int mode, int synchronized);
struct hexp_port_state;
struct hal_port_state;
int hal_init_wripc(struct hal_port_state *hal_ports, char *logfilename);
int hal_update_wripc(int ms_timeout);
int hal_add_cleanup_callback(hal_cleanup_callback_t cb);
int hal_port_start_lock(const char *port_name, int priority);
int hal_port_check_lock(const char *port_name);
int hal_port_reset(const char *port_name);
int hal_port_enable_tracking(const char *port_name);
int hal_port_poll_rts_state(void);
int hal_init_timing_mode(void);
int hal_init_timing(char *filename);
int hal_get_timing_mode(void);
int hal_update_timing_mode(void);
int hal_port_pshifter_busy(void);
#endif
The state machine chart has been created using the plug-in Yakindu in Eclipse
Yakindu web site: https://www.itemis.com/en/yakindu/state-machine/
\ No newline at end of file
/*
* hal.h
*
* Created on: Oct 3, 2019
* Author: baujc
*/
#ifndef USERSPACE_WRSW_HAL_HAL_H_
#define USERSPACE_WRSW_HAL_HAL_H_
typedef int32_t Boolean;
typedef int32_t timeOut_t;
#define RESET_RX 1
#define ENABLE_RX 1
#define RESET_TX 1
#define ENABLE_TX 1
#define DMTD_SRC_TXOUTCLK 1
#define DMTD_SRC_RXRECCLK 1
#define BMCR_ANENABLE 1
#define BMCR_ANRESTART 1
extern void timer_init(timeOut_t tmo);
extern void timer_restart(timeOut_t tmo);
extern Boolean is_timer_expired(timeOut_t tmo);
extern void usleep(int);
extern pcs_write(int );
extern rts_enable_ptracker(void);
extern rts_disable_ptracker(void);
extern Boolean meas_phase(void);
extern Boolean check_phase_range(void);
extern Boolean is_tx_reseted(void);
extern Boolean txSetupDoneOnAllPorts(void);
extern void write_tx_calibration_file(void);
extern Boolean is_rx_aligned(void);
#endif /* USERSPACE_WRSW_HAL_HAL_H_ */
<html>
<body>
<table border="0" cellspacing="0" cellpadding="0" align="LEFT">
<tr>
<td><img src="file:///opt/baujc/project/wr/wrs-proposed-master/wr-switch-sw-hal-review/userspace/wrsw_hal/yakindu/hal_0_0.png"/></td>
</tr>
</table>
</body>
</html>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
-include ../Makefile.specific
# We are now Kconfig-based
-include ../../.config
......
......@@ -71,66 +71,66 @@ extern struct wrs_shm_head *hal_head;
extern struct hal_port_state *hal_ports;
extern int hal_nports_local;
#define HAL_SHMEM_TOTAL_US (20000000) // 20sec
#define HAL_SHMEM_SLEEP_US (250000) // 250ms
#define HAL_SHMEM_RETRIES (HAL_SHMEM_TOTAL_US/HAL_SHMEM_SLEEP_US)
void init_shm(void)
{
struct hal_shmem_header *h;
int ii;
struct hal_shmem_header *hal_shem_header;
int n_wait = 0;
int ret;
/* wait for HAL */
while ((ret = wrs_shm_get_and_check(wrs_shm_hal, &hal_head)) != 0) {
n_wait++;
if (n_wait > 10) {
if (n_wait > HAL_SHMEM_RETRIES) {
/* print if waiting more than 10 seconds, some wait
* is expected since hal requires few seconds to start
*/
if (ret == WRS_SHM_OPEN_FAILED) {
pr_error("Unable to open HAL's shmem!\n");
pr_error("Unable to open HAL's shared memory!\n");
}
if (ret == WRS_SHM_WRONG_VERSION) {
pr_error("Unable to read HAL's version!\n");
}
if (ret == WRS_SHM_INCONSISTENT_DATA) {
pr_error("Unable to read consistent data from "
"HAL's shmem!\n");
pr_error("Unable to read HAL's shared memory version!\n");
}
exit(1);
}
sleep(1);
usleep(HAL_SHMEM_SLEEP_US);
}
h = (void *)hal_head + hal_head->data_off;
while (1) { /* wait 10 sec for HAL to produce consistent nports */
ii = wrs_shm_seqbegin(hal_head);
/* Assume number of ports does not change in runtime */
hal_nports_local = h->nports;
if (!wrs_shm_seqretry(hal_head, ii))
break;
if (n_wait > 10) {
pr_error("Wait for HAL.\n");
exit(1);
}
n_wait++;
sleep(1);
}
hal_shem_header = (void *)hal_head + hal_head->data_off;
/* check hal's shm version */
if (hal_head->version != HAL_SHMEM_VERSION) {
pr_error("Unknown hal's shm version %i (known is %i)\n",
pr_error("Unexpected HAL's shared memory version %i (expected is %i)\n",
hal_head->version, HAL_SHMEM_VERSION);
exit(-1);
}
// Wait end of HAL initialization to produce consistent nports
n_wait=0;
while (1) {
if ( hal_shem_header->shmemState==HAL_SHMEM_STATE_INITITALIZED )
break;
if (n_wait > HAL_SHMEM_RETRIES) {
pr_error("HAL initialization waiting failure\n");
exit(1);
}
n_wait++;
usleep(HAL_SHMEM_SLEEP_US);
}
/* Even after HAL restart, HAL will place structures at the same
* addresses. No need to re-dereference pointer at each read. */
hal_ports = wrs_shm_follow(hal_head, h->ports);
hal_nports_local = hal_shem_header->nports;
hal_ports = wrs_shm_follow(hal_head, hal_shem_header->ports);
if (hal_nports_local > HAL_MAX_PORTS) {
pr_error("Too many ports reported by HAL. %d vs %d supported\n",
hal_nports_local, HAL_MAX_PORTS);
exit(-1);
}
pr_info("Connected to HAL's share memory.\n");
}
/**
......
......@@ -108,7 +108,7 @@ static int rtu_create_static_entries(void)
for (i = 0; i < hal_nports_local; i++) {
enabled_port_mask |= (1 << hal_ports_local_copy[i].hw_index);
port_was_up[i] = state_up(hal_ports_local_copy[i].state);
port_was_up[i] = state_up(&hal_ports_local_copy[i]);
}
// VLAN-aware Bridge reserved addresses (802.1Q-2005 Table 8.1)
......@@ -180,7 +180,7 @@ static void rtu_update_ports_state(void)
if (!hal_ports_local_copy[i].in_use)
continue;
link_up = state_up(hal_ports_local_copy[i].state);
link_up = state_up(&hal_ports_local_copy[i]);
if (port_was_up[i] && !link_up) {
pr_info(
"Port %s went down, removing corresponding entries...\n",
......@@ -253,7 +253,7 @@ static int rtu_daemon_learning_process(void)
for (port_down = i = 0; i <= MAX_PORT; i++) {
p = &hal_ports_local_copy[i];
if (p->in_use && p->hw_index == req.port_id
&& !state_up(p->state)) {
&& !state_up(p)) {
port_down = 1;
pr_debug("port down %s\n", p->name);
break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment