diff mbox

[1/3] Introduce new phy_link and phy_instance abstraction

Message ID 1454672483-15150-1-git-send-email-laforge@gnumonks.org
State Accepted
Headers show

Commit Message

Harald Welte Feb. 5, 2016, 11:41 a.m. UTC
This way we can model a flexible mapping between any number of PHYs,
each having multiple instances, and then map BTSs with TRXx on top of
those PHYs.
---
 doc/examples/octphy/osmo-bts.cfg    |   8 +-
 doc/phy_link.txt                    |  57 ++++++
 doc/startup.txt                     |  42 ++++
 include/osmo-bts/Makefile.am        |   2 +-
 include/osmo-bts/bts.h              |   2 +-
 include/osmo-bts/bts_model.h        |   8 +
 include/osmo-bts/gsm_data.h         |  15 --
 include/osmo-bts/phy_link.h         | 115 +++++++++++
 include/osmo-bts/scheduler.h        |   4 +-
 include/osmo-bts/vty.h              |   7 +-
 src/common/Makefile.am              |   2 +-
 src/common/bts.c                    |  30 ++-
 src/common/main.c                   |  20 ++
 src/common/phy_link.c               | 159 +++++++++++++++
 src/common/scheduler.c              |   6 +-
 src/common/vty.c                    | 194 +++++++++++++++++-
 src/osmo-bts-octphy/l1_if.c         | 170 +++++++++++-----
 src/osmo-bts-octphy/l1_if.h         |  31 ++-
 src/osmo-bts-octphy/l1_oml.c        | 163 ++++++++-------
 src/osmo-bts-octphy/main.c          |   3 +-
 src/osmo-bts-octphy/octphy_hw_api.c |  14 +-
 src/osmo-bts-octphy/octphy_vty.c    | 198 +++++++++++--------
 src/osmo-bts-trx/l1_if.c            |  84 ++++----
 src/osmo-bts-trx/l1_if.h            |  18 +-
 src/osmo-bts-trx/main.c             |  78 +++-----
 src/osmo-bts-trx/scheduler_trx.c    |  33 ++--
 src/osmo-bts-trx/trx_if.c           | 133 +++++++++----
 src/osmo-bts-trx/trx_if.h           |   1 +
 src/osmo-bts-trx/trx_vty.c          | 381 ++++++++++++++++++++++--------------
 29 files changed, 1424 insertions(+), 554 deletions(-)
 create mode 100644 doc/phy_link.txt
 create mode 100644 doc/startup.txt
 create mode 100644 include/osmo-bts/phy_link.h
 create mode 100644 src/common/phy_link.c

Comments

Holger Freyther Feb. 5, 2016, 12:37 p.m. UTC | #1
> On 05 Feb 2016, at 12:41, Harald Welte <laforge@gnumonks.org> wrote:
> 
> 
> +struct phy_link *phy_link_create(void *ctx, int num)
> +{
> +	struct phy_link *plink = talloc_zero(ctx, struct phy_link);
> +
> +	if (phy_link_by_num(num))
> +		return NULL;
> +
> +	plink = talloc_zero(ctx, struct phy_link);


small memleak. plink being assigned twice.


> 
> +struct phy_instance *phy_instance_create(struct phy_link *plink, int num)
> +{
> +	struct phy_instance *pinst = talloc_zero(plink, struct phy_instance);
> +
> +	if (phy_instance_by_num(plink, num))
> +		return NULL;
> +
> +	pinst = talloc_zero(plink, struct phy_instance);

same small leak.





> @@ -518,6 +522,7 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c
> 	l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
> 			  cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID);
> 
> +	pcc->TrxId.byTrxId = pinst->u.octphy.trx_id;


ah nice, is there another way we could check that all primitives have all zero based indices set?




okay ran out of time here. The octphy change looked good, mechanical, already fixing byTrxId assignment in case there will be more than one phy.

holger
Harald Welte Feb. 5, 2016, 12:57 p.m. UTC | #2
Hi Holger,

On Fri, Feb 05, 2016 at 01:37:00PM +0100, Holger Freyther wrote:

> small memleak. plink being assigned twice.

thanks, fixed both now.

> > +	pcc->TrxId.byTrxId = pinst->u.octphy.trx_id;
> 
> ah nice, is there another way we could check that all primitives have
> all zero based indices set?

I'm not sure what you mean here?  AFAIK, the byTrxId can have arbitrary
values from the PHY side, we just need to use the same value as used in
the TRX-OPEN.req for all primitives belinging to that TRX.  However,
internally we use them with 0...n.

> okay ran out of time here. The octphy change looked good, mechanical,
> already fixing byTrxId assignment in case there will be more than one
> phy.

FYI: In the OCTPHY caes, we can actually have a single board
(OCTBTS3500) which has two DSPs.  To each of them we have pne PHY Link,
and in each of them we can have multiple Software TRX, which then map to
PHY instacnes on the OsmoBTS side.  Let's say you have two Soft TRX in
each DSP:
* in terms of gsm_bts_trx numbers, we have TRX 0 .. 3 to match our data
  model (and the way how the GSM specs deal with multiple TRX)
* in terms of byTrxId, we will have 0+1 on each of the PHY links

Regards,
	Harald
Holger Freyther Feb. 5, 2016, 1 p.m. UTC | #3
> On 05 Feb 2016, at 13:57, Harald Welte <laforge@gnumonks.org> wrote:
> 
>> 
> 
> I'm not sure what you mean here?  AFAIK, the byTrxId can have arbitrary
> values from the PHY side, we just need to use the same value as used in
> the TRX-OPEN.req for all primitives belinging to that TRX.  However,
> internally we use them with 0...n.


I wondered which other fields we should have set and that work because talloc has used a memset on the msgb. For trx==0 there is no change but with trx != 0 there would be a fallout.
diff mbox

Patch

diff --git a/doc/examples/octphy/osmo-bts.cfg b/doc/examples/octphy/osmo-bts.cfg
index 07bcae0..4f27c8d 100644
--- a/doc/examples/octphy/osmo-bts.cfg
+++ b/doc/examples/octphy/osmo-bts.cfg
@@ -20,9 +20,13 @@  log stderr
 line vty
  no login
 !
+phy 0
+ octphy hw-addr 00:0C:90:2e:80:1e
+ octphy net-device eth0.2342
+ instance 0
 bts 0
  band 1800
  ipa unit-id 1234 0
  oml remote-ip 127.0.0.1
- phy-hw-addr 00:0C:90:2e:80:1e
- phy-netdev eth0.2342
+ trx 0
+  phy 0 instance 0
diff --git a/doc/phy_link.txt b/doc/phy_link.txt
new file mode 100644
index 0000000..c49e328
--- /dev/null
+++ b/doc/phy_link.txt
@@ -0,0 +1,57 @@ 
+
+== OsmoBTS PHY interface abstraction
+
+The OsmoBTS PHY interface serves as an abstraction layer between given
+PHY hardware and the actual logical transceivers (TRXs) of a BTS inside
+the OsmoBTS code base.
+
+
+=== PHY link
+
+A PHY link is a physical connection / link towards a given PHY.  This
+might be, for example,
+
+* a set of file descriptors to device nodes in the /dev/ directory
+  (sysmobts, litecell15)
+* a packet socket for sending raw Ethernet frames to an OCTPHY
+* a set of UDP sockets for interacting with OsmoTRX
+
+Each PHY interface has a set of attribute/parameters and a list of 1 to
+n PHY instances.
+
+PHY links are numbered 0..n globally inside OsmoBTS.
+
+Each PHY link is configured via the VTY using its individual top-level
+vty node.  Given the different bts-model / phy specific properties, the
+VTY configuration options (if any) of the PHY instance differ between
+BTS models.
+
+The PHY links and instances must be configured above the BTS/TRX nodes
+in the configuration file.  If the file is saved via the VTY, the code
+automatically ensures this.
+
+
+=== PHY instance
+
+A PHY instance is an instance of a PHY, accessed via a PHY link.
+
+In the case of osmo-bts-sysmo and osmo-bts-trx, there is only one
+instance in every PHY link.  This is due to the fact that the API inside
+that PHY link does not permit for distinguishing multiple different
+logical TRXs.
+
+Other PHY implementations like the OCTPHY however do support addressing
+multiple PHY instances via a single PHY link.
+
+PHY instances are numbered 0..n inside each PHY link.
+
+Each PHY instance is configured via the VTY as a separate node beneath each
+PHY link.  Given the different bts-model / phy specific properties, the
+VTY configuration options (if any) of the PHY instance differ between
+BTS models.
+
+
+=== Mapping PHY instances to TRXs
+
+Each TRX node in the VTY must use the 'phy N instance M' command in
+order to specify which PHY instance is allocated to this specific TRX.
diff --git a/doc/startup.txt b/doc/startup.txt
new file mode 100644
index 0000000..cc64375
--- /dev/null
+++ b/doc/startup.txt
@@ -0,0 +1,42 @@ 
+
+== start-up / sequencing during OsmoBTS start
+
+The start-up procedure of OsmoBTS can be described as follows:
+
+|===
+| bts-specific | main() |
+| common | bts_main() | initialization of talloc contexts
+| common | bts_log_init() | initialization of logging
+| common | handle_options() | common option parsing
+| bts-specific | bts_model_handle_options() | model-specific option parsing
+| common | gsm_bts_alloc() | allocation of BTS/TRX/TS data structures
+| common | vty_init() | Initialziation of VTY core, libosmo-abis and osmo-bts VTY
+| common | main() | Setting of scheduler RR priority (if configured)
+| common | main() | Initialization of GSMTAP (if configured)
+| common | bts_init() | configuration of defaults in bts/trx/s object
+| bts-specific | bts_model_init | ?
+| common | abis_init() | Initialization of libosmo-abis
+| common | vty_read_config_file() | Reading of configuration file
+| bts-specific | bts_model_phy_link_set_defaults() | Called for every PHY link created
+| bts-specific | bts_model_phy_instance_set_defaults() | Called for every PHY Instance created
+| common | bts_controlif_setup() | Initialization of Control Interface
+| bts-specific | bts_model_ctrl_cmds_install()
+| common | telnet_init() | Initialization of telnet interface
+| common | pcu_sock_init() | Initializaiton of PCU socket
+| common | main() | Installation of signal handlers
+| common | abis_open() | Start of the A-bis connection to BSC
+| common | phy_links_open() | Iterate over list of configured PHY links
+| bts-specific | bts_model_phy_link_open() | Open each of the configured PHY links
+| common | write_pid_file() | Generate the pid file
+| common | osmo_daemonize() | Fork as daemon in background (if configured)
+| common | bts_main() | Run main loop until global variable quit >= 2
+| bts-specific | bts_model_oml_estab() | Called by core once OML link is established
+| bts-specific | bts_model_check_oml() | called each time OML sets some attributes on a MO, checks if attributes are valid
+| bts-specific | bts_model_apply_oml() | called each time OML sets some attributes on a MO, stores attribute contents in data structures
+| bts-specific | bts_model_opstart() | for NM_OC_BTS, NM_OC_SITE_MANAGER, NM_OC_GPRS_NSE, NM_OC_GPRS_CELL, NMO_OC_GPRS_NSVC
+| bts-specific | bts_model_opstart() | for NM_OC_RADIO_CARRIER for each trx
+| bts-specific | bts_model_opstart() | for NM_OC_BASEB_TRANSC for each trx
+| bts-specific | bts_model_opstart() | for NM_OC_CHANNEL for each timeslot on each trx
+| bts-specific | bts_model_change_power() | change transmit power for each trx (power ramp-up/ramp-down
+
+| bts-specific | bts_model_abis_close() | called when either one of the RSL links or the OML link are down
diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am
index af88a1a..d0b55ff 100644
--- a/include/osmo-bts/Makefile.am
+++ b/include/osmo-bts/Makefile.am
@@ -1,4 +1,4 @@ 
 noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \
 		 oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \
 		 handover.h msg_utils.h tx_power.h control_if.h cbch.h l1sap.h \
-		 power_control.h scheduler.h scheduler_backend.h
+		 power_control.h scheduler.h scheduler_backend.h phy_link.h
diff --git a/include/osmo-bts/bts.h b/include/osmo-bts/bts.h
index 9e21186..ec58edd 100644
--- a/include/osmo-bts/bts.h
+++ b/include/osmo-bts/bts.h
@@ -21,12 +21,12 @@  void destroy_bts(struct gsm_bts *bts);
 int work_bts(struct gsm_bts *bts);
 int bts_link_estab(struct gsm_bts *bts);
 int trx_link_estab(struct gsm_bts_trx *trx);
+int trx_set_available(struct gsm_bts_trx *trx, int avail);
 void bts_new_si(void *arg);
 void bts_setup_slot(struct gsm_bts_trx_ts *slot, uint8_t comb);
 
 int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg);
 struct msgb *bts_agch_dequeue(struct gsm_bts *bts);
-void bts_update_agch_max_queue_length(struct gsm_bts *bts);
 int bts_agch_max_queue_length(int T, int bcch_conf);
 int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt,
 		      int is_ag_res);
diff --git a/include/osmo-bts/bts_model.h b/include/osmo-bts/bts_model.h
index 41b5e93..6252557 100644
--- a/include/osmo-bts/bts_model.h
+++ b/include/osmo-bts/bts_model.h
@@ -8,6 +8,9 @@ 
 
 #include <osmo-bts/gsm_data.h>
 
+struct phy_link;
+struct phy_instance;
+
 /* BTS model specific functions needed by the common code */
 
 int bts_model_init(struct gsm_bts *bts);
@@ -32,6 +35,8 @@  int bts_model_vty_init(struct gsm_bts *bts);
 
 void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts);
 void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx);
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink);
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst);
 
 int bts_model_oml_estab(struct gsm_bts *bts);
 
@@ -47,4 +52,7 @@  int bts_model_ctrl_cmds_install(struct gsm_bts *bts);
 int bts_model_handle_options(int argc, char **argv);
 void bts_model_print_help();
 
+void bts_model_phy_link_set_defaults(struct phy_link *plink);
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst);
+
 #endif
diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h
index 6a7bca8..f9e6ed1 100644
--- a/include/osmo-bts/gsm_data.h
+++ b/include/osmo-bts/gsm_data.h
@@ -119,21 +119,6 @@  enum lchan_ciph_state {
 
 #include "openbsc/gsm_data_shared.h"
 
-struct femtol1_hdl;
-
-static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx)
-{
-	return trx->role_bts.l1h;
-}
-
-struct trx_l1h;
-
-static inline struct trx_l1h *trx_l1h_hdl(struct gsm_bts_trx *trx)
-{
-	return trx->role_bts.l1h;
-}
-
-
 void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state);
 
 /* cipher code */
diff --git a/include/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h
new file mode 100644
index 0000000..b9fc412
--- /dev/null
+++ b/include/osmo-bts/phy_link.h
@@ -0,0 +1,115 @@ 
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <osmo-bts/scheduler.h>
+
+#include <linux/if_packet.h>
+
+struct gsm_bts_trx;
+
+enum phy_link_type {
+	PHY_LINK_T_NONE,
+	PHY_LINK_T_SYSMOBTS,
+	PHY_LINK_T_OSMOTRX,
+};
+
+enum phy_link_state {
+	PHY_LINK_SHUTDOWN,
+	PHY_LINK_CONNECTING,
+	PHY_LINK_CONNECTED,
+};
+
+/* A PHY link represents the connection to a given PHYsical layer
+ * implementation.  That PHY link contains 1...N PHY instances, one for
+ * each TRX */
+struct phy_link {
+	struct llist_head list;
+	int num;
+	enum phy_link_type type;
+	enum phy_link_state state;
+	struct llist_head instances;
+	char *description;
+	union {
+		struct {
+		} sysmobts;
+		struct {
+			char *transceiver_ip;
+			uint16_t base_port_local;
+			uint16_t base_port_remote;
+			struct osmo_fd trx_ofd_clk;
+
+			uint32_t clock_advance;
+			uint32_t rts_advance;
+
+			int	rxgain_valid;
+			int	rxgain;
+			int	rxgain_sent;
+
+			int	power_valid;
+			int	power;
+			int	power_oml;
+			int	power_sent;
+		} osmotrx;
+		struct {
+			/* MAC address of the PHY */
+			struct sockaddr_ll phy_addr;
+			/* Network device name */
+			char *netdev_name;
+
+			/* configuration */
+			uint32_t rf_port_index;
+			uint32_t rx_gain_db;
+			uint32_t tx_atten_db;
+
+			struct octphy_hdl *hdl;
+		} octphy;
+	} u;
+};
+
+struct phy_instance {
+	/* liked inside phy_link.linstances */
+	struct llist_head list;
+	int num;
+	char *description;
+
+	/* pointer to the PHY link to which we belong */
+	struct phy_link *phy_link;
+
+	/* back-pointer to the TRX to which we're associated */
+	struct gsm_bts_trx *trx;
+
+	union {
+		struct {
+		} sysmobts;
+		struct {
+			struct trx_l1h *hdl;
+		} osmotrx;
+		struct {
+			/* logical transceiver number within one PHY */
+			uint32_t trx_id;
+		} octphy;
+	} u;
+};
+
+struct phy_link *phy_link_by_num(int num);
+struct phy_link *phy_link_create(void *ctx, int num);
+void phy_link_destroy(struct phy_link *plink);
+void phy_link_state_set(struct phy_link *plink, enum phy_link_state state);
+int phy_links_open(void);
+
+struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num);
+struct phy_instance *phy_instance_create(struct phy_link *plink, int num);
+void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx);
+void phy_instance_destroy(struct phy_instance *pinst);
+const char *phy_instance_name(struct phy_instance *pinst);
+
+void phy_user_statechg_notif(struct phy_instance *pinst, enum phy_link_state link_state);
+
+static inline struct phy_instance *trx_phy_instance(struct gsm_bts_trx *trx)
+{
+	return trx->role_bts.l1h;
+}
+
+int bts_model_phy_link_open(struct phy_link *plink);
diff --git a/include/osmo-bts/scheduler.h b/include/osmo-bts/scheduler.h
index 13ef051..b11e6f1 100644
--- a/include/osmo-bts/scheduler.h
+++ b/include/osmo-bts/scheduler.h
@@ -1,6 +1,8 @@ 
 #ifndef TRX_SCHEDULER_H
 #define TRX_SCHEDULER_H
 
+#include <osmo-bts/gsm_data.h>
+
 /* These types define the different channels on a multiframe.
  * Each channel has queues and can be activated individually.
  */
@@ -133,7 +135,7 @@  extern uint32_t transceiver_last_fn;
 
 
 /*! \brief Initialize the scheudler data structures */
-int trx_sched_init(struct l1sched_trx *l1t);
+int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx);
 
 /*! \brief De-initialize the scheudler data structures */
 void trx_sched_exit(struct l1sched_trx *l1t);
diff --git a/include/osmo-bts/vty.h b/include/osmo-bts/vty.h
index 546729c..5d8d4a7 100644
--- a/include/osmo-bts/vty.h
+++ b/include/osmo-bts/vty.h
@@ -5,7 +5,12 @@ 
 #include <osmocom/vty/command.h>
 
 enum bts_vty_node {
-	BTS_NODE = _LAST_OSMOVTY_NODE + 1,
+	/* PHY_NODE must come before BTS node to ensure the phy
+	 * instances are created at the time the TRX nodes want to refer
+	 * to them */
+	PHY_NODE = _LAST_OSMOVTY_NODE + 1,
+	PHY_INST_NODE,
+	BTS_NODE,
 	TRX_NODE,
 };
 
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index fea205c..8df6513 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -8,6 +8,6 @@  libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \
 		   load_indication.c pcu_sock.c handover.c msg_utils.c \
 		   load_indication.c pcu_sock.c handover.c msg_utils.c \
 		   tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \
-		   l1sap.c cbch.c power_control.c main.c
+		   l1sap.c cbch.c power_control.c main.c phy_link.c
 
 libl1sched_a_SOURCES = scheduler.c
diff --git a/src/common/bts.c b/src/common/bts.c
index 43f4c25..2d0ad8e 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -44,6 +44,7 @@ 
 #include <osmo-bts/oml.h>
 #include <osmo-bts/signal.h>
 
+static void bts_update_agch_max_queue_length(struct gsm_bts *bts);
 
 struct gsm_network bts_gsmnet = {
 	.bts_list = { &bts_gsmnet.bts_list, &bts_gsmnet.bts_list },
@@ -72,6 +73,8 @@  static int bts_signal_cbfn(unsigned int subsys, unsigned int signal,
 	return 0;
 }
 
+/* Initialize the BTS (and TRX) data structures, called before config
+ * file reading */
 int bts_init(struct gsm_bts *bts)
 {
 	struct gsm_bts_role_bts *btsb;
@@ -255,6 +258,31 @@  int trx_link_estab(struct gsm_bts_trx *trx)
 	return 0;
 }
 
+/* set the availability of the TRX (used by PHY driver) */
+int trx_set_available(struct gsm_bts_trx *trx, int avail)
+{
+	int tn;
+
+	LOGP(DSUM, LOGL_INFO, "TRX(%d): Setting available = %d\n",
+		trx->nr, avail);
+	if (avail) {
+		oml_mo_state_chg(&trx->mo,  NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+		oml_mo_tx_sw_act_rep(&trx->mo);
+		oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+		oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+
+		for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++)
+			oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+	} else {
+		oml_mo_state_chg(&trx->mo,  NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+		oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OFF_LINE);
+
+		for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++)
+			oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+	}
+	return 0;
+}
+
 int lchan_init_lapdm(struct gsm_lchan *lchan)
 {
 	struct lapdm_channel *lc = &lchan->lapdm_ch;
@@ -310,7 +338,7 @@  int bts_agch_max_queue_length(int T, int bcch_conf)
 	return (T + 2 * S) * ccch_rach_ratio256 / 256;
 }
 
-void bts_update_agch_max_queue_length(struct gsm_bts *bts)
+static void bts_update_agch_max_queue_length(struct gsm_bts *bts)
 {
 	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
 	struct gsm48_system_information_type_3 *si3;
diff --git a/src/common/main.c b/src/common/main.c
index fabe482..e454a28 100644
--- a/src/common/main.c
+++ b/src/common/main.c
@@ -42,6 +42,7 @@ 
 #include <osmocom/core/gsmtap.h>
 
 #include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
 #include <osmo-bts/logging.h>
 #include <osmo-bts/abis.h>
 #include <osmo-bts/bts.h>
@@ -284,6 +285,19 @@  int bts_main(int argc, char **argv)
 		exit(1);
 	}
 
+	if (!phy_link_by_num(0)) {
+		fprintf(stderr, "You need to configure at last phy0\n");
+		exit(1);
+	}
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (!trx->role_bts.l1h) {
+			fprintf(stderr, "TRX %u has no associated PHY instance\n",
+				trx->nr);
+			exit(1);
+		}
+	}
+
 	write_pid_file("osmo-bts");
 
 	bts_controlif_setup(bts);
@@ -317,6 +331,12 @@  int bts_main(int argc, char **argv)
 		exit(2);
 	}
 
+	rc = phy_links_open();
+	if (rc < 0) {
+		fprintf(stderr, "unable ot open PHY link(s)\n");
+		exit(2);
+	}
+
 	if (daemonize) {
 		rc = osmo_daemonize();
 		if (rc < 0) {
diff --git a/src/common/phy_link.c b/src/common/phy_link.c
new file mode 100644
index 0000000..4103ede
--- /dev/null
+++ b/src/common/phy_link.c
@@ -0,0 +1,159 @@ 
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/logging.h>
+
+static LLIST_HEAD(g_phy_links);
+
+struct phy_link *phy_link_by_num(int num)
+{
+	struct phy_link *plink;
+
+	llist_for_each_entry(plink, &g_phy_links, list) {
+		if (plink->num == num)
+			return plink;
+	}
+
+	return NULL;
+}
+
+struct phy_link *phy_link_create(void *ctx, int num)
+{
+	struct phy_link *plink = talloc_zero(ctx, struct phy_link);
+
+	if (phy_link_by_num(num))
+		return NULL;
+
+	plink = talloc_zero(ctx, struct phy_link);
+	plink->num = num;
+	plink->state = PHY_LINK_SHUTDOWN;
+	INIT_LLIST_HEAD(&plink->instances);
+	llist_add_tail(&plink->list, &g_phy_links);
+
+	bts_model_phy_link_set_defaults(plink);
+
+	return plink;
+}
+
+const struct value_string phy_link_state_vals[] = {
+	{ PHY_LINK_SHUTDOWN, 	"shutdown" },
+	{ PHY_LINK_CONNECTING,	"connectiong" },
+	{ PHY_LINK_CONNECTED,	"connected" },
+	{ 0, NULL }
+};
+
+void phy_link_state_set(struct phy_link *plink, enum phy_link_state state)
+{
+	struct phy_instance *pinst;
+
+	LOGP(DL1C, LOGL_INFO, "PHY link state change %s -> %s\n",
+	     get_value_string(phy_link_state_vals, plink->state),
+	     get_value_string(phy_link_state_vals, state));
+
+	/* notify all TRX associated with this phy */
+	llist_for_each_entry(pinst, &plink->instances, list) {
+		struct gsm_bts_trx *trx = pinst->trx;
+		if (!trx)
+			continue;
+
+		switch (state) {
+		case PHY_LINK_CONNECTED:
+			LOGP(DL1C, LOGL_INFO, "trx_set_avail(1)\n");
+			trx_set_available(trx, 1);
+			break;
+		default:
+			LOGP(DL1C, LOGL_INFO, "trx_set_avail(0)\n");
+			trx_set_available(trx, 0);
+			break;
+		}
+	}
+
+	plink->state = state;
+}
+
+struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num)
+{
+	struct phy_instance *pinst;
+
+	llist_for_each_entry(pinst, &plink->instances, list) {
+		if (pinst->num == num)
+			return pinst;
+	}
+	return NULL;
+}
+
+struct phy_instance *phy_instance_create(struct phy_link *plink, int num)
+{
+	struct phy_instance *pinst = talloc_zero(plink, struct phy_instance);
+
+	if (phy_instance_by_num(plink, num))
+		return NULL;
+
+	pinst = talloc_zero(plink, struct phy_instance);
+	pinst->num = num;
+	pinst->phy_link = plink;
+	llist_add_tail(&pinst->list, &plink->instances);
+
+	bts_model_phy_instance_set_defaults(pinst);
+
+	return pinst;
+}
+
+void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx)
+{
+	trx->role_bts.l1h = pinst;
+	pinst->trx = trx;
+}
+
+void phy_instance_destroy(struct phy_instance *pinst)
+{
+	/* remove from list of instances in the link */
+	llist_del(&pinst->list);
+
+	/* remove reverse link from TRX */
+	OSMO_ASSERT(pinst->trx->role_bts.l1h == pinst);
+	pinst->trx->role_bts.l1h = NULL;
+	pinst->trx = NULL;
+
+	talloc_free(pinst);
+}
+
+void phy_link_destroy(struct phy_link *plink)
+{
+	struct phy_instance *pinst, *pinst2;
+
+	llist_for_each_entry_safe(pinst, pinst2, &plink->instances, list)
+		phy_instance_destroy(pinst);
+
+	talloc_free(plink);
+}
+
+int phy_links_open(void)
+{
+	struct phy_link *plink;
+
+	llist_for_each_entry(plink, &g_phy_links, list) {
+		int rc;
+
+		rc = bts_model_phy_link_open(plink);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+const char *phy_instance_name(struct phy_instance *pinst)
+{
+	static char buf[32];
+
+	snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num,
+		 pinst->num);
+	return buf;
+}
diff --git a/src/common/scheduler.c b/src/common/scheduler.c
index 64d89ac..de09fbf 100644
--- a/src/common/scheduler.c
+++ b/src/common/scheduler.c
@@ -134,11 +134,13 @@  const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
  * init / exit
  */
 
-int trx_sched_init(struct l1sched_trx *l1t)
+int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx)
 {
 	uint8_t tn;
 	int i;
 
+	l1t->trx = trx;
+
 	LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr);
 
 	for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
@@ -191,7 +193,7 @@  void trx_sched_exit(struct l1sched_trx *l1t)
 void trx_sched_reset(struct l1sched_trx *l1t)
 {
 	trx_sched_exit(l1t);
-	trx_sched_init(l1t);
+	trx_sched_init(l1t, l1t->trx);
 }
 
 struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn,
diff --git a/src/common/vty.c b/src/common/vty.c
index 4fd65d0..94ccdbe 100644
--- a/src/common/vty.c
+++ b/src/common/vty.c
@@ -40,6 +40,7 @@ 
 
 #include <osmo-bts/logging.h>
 #include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
 #include <osmo-bts/abis.h>
 #include <osmo-bts/bts.h>
 #include <osmo-bts/rsl.h>
@@ -53,6 +54,13 @@ 
 int bts_vty_go_parent(struct vty *vty)
 {
 	switch (vty->node) {
+	case PHY_INST_NODE:
+		vty->node = PHY_NODE;
+		{
+			struct phy_instance *pinst = vty->index;
+			vty->index = pinst->phy_link;
+		}
+		break;
 	case TRX_NODE:
 		vty->node = BTS_NODE;
 		{
@@ -60,6 +68,7 @@  int bts_vty_go_parent(struct vty *vty)
 			vty->index = trx->bts;
 		}
 		break;
+	case PHY_NODE:
 	default:
 		vty->node = CONFIG_NODE;
 	}
@@ -71,6 +80,8 @@  int bts_vty_is_config_node(struct vty *vty, int node)
 	switch (node) {
 	case TRX_NODE:
 	case BTS_NODE:
+	case PHY_NODE:
+	case PHY_INST_NODE:
 		return 1;
 	default:
 		return 0;
@@ -81,6 +92,17 @@  gDEFUN(ournode_exit, ournode_exit_cmd, "exit",
 	"Exit current node, go down to provious node")
 {
 	switch (vty->node) {
+	case PHY_INST_NODE:
+		vty->node = PHY_NODE;
+		{
+			struct phy_instance *pinst = vty->index;
+			vty->index = pinst->phy_link;
+		}
+		break;
+	case PHY_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
 	case TRX_NODE:
 		vty->node = BTS_NODE;
 		{
@@ -141,6 +163,7 @@  static struct cmd_node trx_node = {
 	1,
 };
 
+
 DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
 	"trx <0-254>",
 	"Select a TRX to configure\n" "TRX number\n")
@@ -151,7 +174,7 @@  DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
 
 	trx = gsm_bts_trx_num(bts, trx_nr);
 	if (!trx) {
-		vty_out(vty, "Unknown TRX %u. Aavialable TRX are: 0..%d%s",
+		vty_out(vty, "Unknown TRX %u. Aavialable TRX are: 0..%u%s",
 			trx_nr, bts->num_trx - 1, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
@@ -232,6 +255,7 @@  static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
 
 	llist_for_each_entry(trx, &bts->trx_list, list) {
 		struct trx_power_params *tpp = &trx->power_params;
+		struct phy_instance *pinst = trx_phy_instance(trx);
 		vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
 
 		if (trx->power_params.user_gain_mdB)
@@ -246,6 +270,8 @@  static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
 		vty_out(vty, "  ms-power-control %s%s",
 			trx->ms_power_control == 0 ? "dsp" : "osmo",
 			VTY_NEWLINE);
+		vty_out(vty, "  phy %u instance %u%s", pinst->phy_link->num,
+			pinst->num, VTY_NEWLINE);
 
 		bts_model_config_write_trx(vty, trx);
 	}
@@ -262,6 +288,35 @@  static int config_write_bts(struct vty *vty)
 	return CMD_SUCCESS;
 }
 
+static void config_write_phy_single(struct vty *vty, struct phy_link *plink)
+{
+	int i;
+
+	vty_out(vty, "phy %u%s", plink->num, VTY_NEWLINE);
+	bts_model_config_write_phy(vty, plink);
+
+	for (i = 0; i < 255; i++) {
+		struct phy_instance *pinst = phy_instance_by_num(plink, i);
+		if (!pinst)
+			break;
+		vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE);
+	}
+}
+
+static int config_write_phy(struct vty *vty)
+{
+	int i;
+
+	for (i = 0; i < 255; i++) {
+		struct phy_link *plink = phy_link_by_num(i);
+		if (!plink)
+			break;
+		config_write_phy_single(vty, plink);
+	}
+
+	return CMD_SUCCESS;
+}
+
 static int config_write_dummy(struct vty *vty)
 {
 	return CMD_SUCCESS;
@@ -532,6 +587,33 @@  DEFUN(cfg_trx_ms_power_control, cfg_trx_ms_power_control_cmd,
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_trx_phy, cfg_trx_phy_cmd,
+	"phy <0-255> instance <0-255>",
+	"Configure PHY Link+Instance for this TRX\n"
+	"PHY Link number\n" "PHY instance\n" "PHY Instance number")
+{
+	struct gsm_bts_trx *trx = vty->index;
+	struct phy_link *plink = phy_link_by_num(atoi(argv[0]));
+	struct phy_instance *pinst;
+
+	if (!plink) {
+		vty_out(vty, "phy%s does not exist%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	pinst = phy_instance_by_num(plink, atoi(argv[1]));
+	if (!pinst) {
+		vty_out(vty, "phy%s instance %s does not exit%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	trx->role_bts.l1h = pinst;
+	pinst->trx = trx;
+
+	return CMD_SUCCESS;
+}
 
 /* ======================================================================
  * SHOW
@@ -702,6 +784,106 @@  DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd,
 	return CMD_SUCCESS;
 }
 
+static struct cmd_node phy_node = {
+	PHY_NODE,
+	"%s(phy)#",
+	1,
+};
+
+static struct cmd_node phy_inst_node = {
+	PHY_INST_NODE,
+	"%s(phy-inst)#",
+	1,
+};
+
+DEFUN(cfg_phy, cfg_phy_cmd,
+	"phy <0-255>",
+	"Select a PHY to configure\n" "PHY number\n")
+{
+	int phy_nr = atoi(argv[0]);
+	struct phy_link *plink;
+
+	plink = phy_link_by_num(phy_nr);
+	if (!plink)
+		plink = phy_link_create(tall_bts_ctx, phy_nr);
+	if (!plink)
+		return CMD_WARNING;
+
+	vty->index = plink;
+	vty->index_sub = &plink->description;
+	vty->node = PHY_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_inst, cfg_phy_inst_cmd,
+	"instance <0-255>",
+	"Select a PHY instance to configure\n" "PHY Instance number\n")
+{
+	int inst_nr = atoi(argv[0]);
+	struct phy_link *plink = vty->index;
+	struct phy_instance *pinst;
+
+	pinst = phy_instance_by_num(plink, inst_nr);
+	if (!pinst) {
+		pinst = phy_instance_create(plink, inst_nr);
+		if (!pinst) {
+			vty_out(vty, "Unable to create phy%u instance %u%s",
+				plink->num, inst_nr, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	vty->index = pinst;
+	vty->index_sub = &pinst->description;
+	vty->node = PHY_INST_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd,
+	"no instance <0-255>"
+	NO_STR "Select a PHY instance to remove\n", "PHY Instance number\n")
+{
+	int inst_nr = atoi(argv[0]);
+	struct phy_link *plink = vty->index;
+	struct phy_instance *pinst;
+
+	pinst = phy_instance_by_num(plink, inst_nr);
+	if (!pinst) {
+		vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	phy_instance_destroy(pinst);
+
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_phy_type, cfg_phy_type_cmd,
+	"type (sysmobts|osmo-trx|virtual)",
+	"configure the type of the PHY\n"
+	"sysmocom sysmoBTS PHY\n"
+	"OsmoTRX based PHY\n"
+	"Virtual PHY (GSMTAP based)\n")
+{
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "sysmobts"))
+		plink->type = PHY_LINK_T_SYSMOBTS;
+	else if (!strcmp(argv[0], "osmo-trx"))
+		plink->type = PHY_LINK_T_OSMOTRX;
+	else if (!strcmp(argv[0], "virtual"))
+		plink->type = PHY_LINK_T_VIRTUAL;
+}
+#endif
+
 DEFUN(bts_t_t_l_jitter_buf,
 	bts_t_t_l_jitter_buf_cmd,
 	"bts <0-0> trx <0-0> ts <0-7> lchan <0-1> rtp jitter-buffer <0-10000>",
@@ -813,10 +995,20 @@  int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat)
 	install_element(TRX_NODE, &cfg_trx_pr_step_size_cmd);
 	install_element(TRX_NODE, &cfg_trx_pr_step_interval_cmd);
 	install_element(TRX_NODE, &cfg_trx_ms_power_control_cmd);
+	install_element(TRX_NODE, &cfg_trx_phy_cmd);
 
 	install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd);
 	install_element(ENABLE_NODE, &bts_t_t_l_loopback_cmd);
 	install_element(ENABLE_NODE, &no_bts_t_t_l_loopback_cmd);
 
+	install_element(CONFIG_NODE, &cfg_phy_cmd);
+	install_node(&phy_node, config_write_phy);
+	install_default(PHY_NODE);
+	install_element(PHY_NODE, &cfg_phy_inst_cmd);
+	install_element(PHY_NODE, &cfg_phy_no_inst_cmd);
+
+	install_node(&phy_inst_node, config_write_dummy);
+	install_default(PHY_INST_NODE);
+
 	return 0;
 }
diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c
index db6e032..3215aa1 100644
--- a/src/osmo-bts-octphy/l1_if.c
+++ b/src/osmo-bts-octphy/l1_if.c
@@ -38,6 +38,7 @@ 
 #include <osmocom/core/socket.h>
 
 #include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
 #include <osmo-bts/oml.h>
 #include <osmo-bts/logging.h>
 #include <osmo-bts/l1sap.h>
@@ -109,6 +110,17 @@  osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn)
 	}
 };
 
+struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id)
+{
+	struct phy_instance *pinst;
+
+	pinst = phy_instance_by_num(fl1h->phy_link, trx_id);
+	if (!pinst)
+		return NULL;
+
+	return pinst->trx;
+}
+
 struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx,
 				tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id)
 {
@@ -282,10 +294,9 @@  int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg,
 }
 
 /* For OctPHY, this only about sending state changes to BSC */
-int l1if_activate_rf(struct octphy_hdl *fl1h, int on)
+int l1if_activate_rf(struct gsm_bts_trx *trx, int on)
 {
 	int i;
-	struct gsm_bts_trx *trx = fl1h->priv;
 	if (on) {
 		/* signal availability */
 		oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
@@ -422,7 +433,8 @@  static void empty_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_E
 static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
 			struct osmo_phsap_prim *l1sap)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *l1msg = l1p_msgb_alloc();
 	uint32_t u32Fn;
 	uint8_t u8Tn, subCh, sapi = 0;
@@ -489,7 +501,7 @@  static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
 		l1if_fill_msg_hdr(&data_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  	  cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID);
 
-		data_req->TrxId.byTrxId = trx->nr;
+		data_req->TrxId.byTrxId = pinst->u.octphy.trx_id;
 		data_req->LchId.byTimeslotNb = u8Tn;
 		data_req->LchId.bySAPI = sapi;
 		data_req->LchId.bySubChannelNb = subCh;
@@ -508,7 +520,7 @@  static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
 		l1if_fill_msg_hdr(&empty_frame_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  	  cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID);
 
-		empty_frame_req->TrxId.byTrxId = trx->nr;
+		empty_frame_req->TrxId.byTrxId = pinst->u.octphy.trx_id;
 		empty_frame_req->LchId.byTimeslotNb = u8Tn;
 		empty_frame_req->LchId.bySAPI = sapi;
 		empty_frame_req->LchId.bySubChannelNb = subCh;
@@ -527,7 +539,8 @@  done:
 static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
 		      struct osmo_phsap_prim *l1sap)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct gsm_lchan *lchan;
 	uint32_t u32Fn;
 	uint8_t u8Tn, subCh, sapi, ss;
@@ -565,7 +578,7 @@  static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
 		l1if_fill_msg_hdr(&data_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  	  cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID);
 
-		data_req->TrxId.byTrxId = trx->nr;
+		data_req->TrxId.byTrxId = pinst->u.octphy.trx_id;
 		data_req->LchId.byTimeslotNb = u8Tn;
 		data_req->LchId.bySAPI = sapi;
 		data_req->LchId.bySubChannelNb = subCh;
@@ -590,7 +603,7 @@  static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
 		l1if_fill_msg_hdr(&empty_frame_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  	  cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID);
 
-		empty_frame_req->TrxId.byTrxId = trx->nr;
+		empty_frame_req->TrxId.byTrxId = pinst->u.octphy.trx_id;
 		empty_frame_req->LchId.byTimeslotNb = u8Tn;
 		empty_frame_req->LchId.bySAPI = sapi;
 		empty_frame_req->LchId.bySubChannelNb = subCh;
@@ -686,32 +699,78 @@  int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
 	return rc;
 }
 
+static int trx_close_all_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+	tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car =
+		(tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h;
+
+	/* in a completion call-back, we take msgb ownership and must
+	 * release it before returning */
+
+	mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car);
+
+	/* we now know that the PHY link is connected */
+	phy_link_state_set(fl1->phy_link, PHY_LINK_CONNECTED);
+
+	msgb_free(resp);
+
+	return 0;
+}
+
+static int phy_link_trx_close_all(struct phy_link *plink)
+{
+	struct octphy_hdl *fl1h = plink->u.octphy.hdl;
+	struct msgb *msg = l1p_msgb_alloc();
+	tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac;
+
+	cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *)
+				msgb_put(msg, sizeof(*cac));
+	l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+			  cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID);
+
+	mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac);
+
+	return l1if_req_compl(fl1h, msg, trx_close_all_cb, NULL);
+}
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+	if (plink->u.octphy.hdl)
+		l1if_close(plink->u.octphy.hdl);
+
+	phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+	plink->u.octphy.hdl = l1if_open(plink);
+	if (!plink->u.octphy.hdl) {
+		phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+		return -1;
+	}
+
+	/* do we need to iterate over the list of instances and do some
+	 * instance-specific initialization? */
+
+	/* close all TRXs that might still exist in this link from
+	 * previous execitions / sessions */
+	phy_link_trx_close_all(plink);
+
+	/* in the call-back to the above we will set the link state to
+	 * connected */
+
+	return 0;
+}
+
 int bts_model_init(struct gsm_bts *bts)
 {
 	struct gsm_bts_role_bts *btsb;
-	struct octphy_hdl *fl1h;
 
 	LOGP(DL1C, LOGL_NOTICE, "model_init()\n");
 
 	btsb = bts_role_bts(bts);
 	btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
 
-	fl1h = talloc_zero(bts, struct octphy_hdl);
-	if (!fl1h)
-		return -ENOMEM;
-
-	INIT_LLIST_HEAD(&fl1h->wlc_list);
-	INIT_LLIST_HEAD(&fl1h->wlc_postponed);
-	fl1h->priv = bts->c0;
-	bts->c0->role_bts.l1h = fl1h;
 	/* FIXME: what is the nominal transmit power of the PHY/board? */
 	bts->c0->nominal_power = 15;
 
-	/* configure some reasonable defaults, to be overridden by VTY */
-	fl1h->config.rf_port_index = 0;
-	fl1h->config.rx_gain_db = 70;
-	fl1h->config.tx_atten_db = 0;
-
 	bts_model_vty_init(bts);
 
 	return 0;
@@ -750,23 +809,15 @@  static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m)
 
 static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn)
 {
-	struct gsm_bts_trx *trx = fl1->priv;
-	struct gsm_bts *bts = trx->bts;
+	struct gsm_bts_trx *trx = trx_by_l1h(fl1, trx_id);
 	struct osmo_phsap_prim l1sap;
 
 	/* increment the primitive count for the alive timer */
 	fl1->alive_prim_cnt++;
 
 	/* ignore every time indication, except for c0 */
-	if (trx != bts->c0)
-		return 0;
-
-	if (trx_id != trx->nr) {
-		LOGP(DL1C, LOGL_FATAL,
-		     "TRX id %d from response does not match the L1 context trx %d\n",
-		     trx_id, trx->nr);
+	if (trx != trx->bts->c0)
 		return 0;
-	}
 
 	memset(&l1sap, 0, sizeof(l1sap));
 	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
@@ -783,7 +834,7 @@  static int handle_ph_readytosend_ind(struct octphy_hdl *fl1,
 	tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt,
 	struct msgb *l1p_msg)
 {
-	struct gsm_bts_trx *trx = fl1->priv;
+	struct gsm_bts_trx *trx = trx_by_l1h(fl1, evt->TrxId.byTrxId);
 	struct gsm_bts *bts = trx->bts;
 	struct osmo_phsap_prim *l1sap;
 	struct gsm_time g_time;
@@ -908,7 +959,7 @@  static int handle_ph_data_ind(struct octphy_hdl *fl1,
 		tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *data_ind,
 		struct msgb *l1p_msg)
 {
-	struct gsm_bts_trx *trx = fl1->priv;
+	struct gsm_bts_trx *trx = trx_by_l1h(fl1, data_ind->TrxId.byTrxId);
 	uint8_t chan_nr, link_id;
 	struct osmo_phsap_prim *l1sap;
 	uint32_t fn;
@@ -992,7 +1043,7 @@  static int handle_ph_rach_ind(struct octphy_hdl *fl1,
 		tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *ra_ind,
 		struct msgb *l1p_msg)
 {
-	struct gsm_bts_trx *trx = fl1->priv;
+	struct gsm_bts_trx *trx = trx_by_l1h(fl1, ra_ind->TrxId.byTrxId);
 	struct gsm_bts *bts = trx->bts;
 	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
 	struct gsm_lchan *lchan;
@@ -1131,7 +1182,7 @@  static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id)
 			if (wlc->cb) {
 				/* call-back function must take msgb
 				 * ownership. */
-				rc = wlc->cb(fl1h->priv, msg, wlc->cb_data);
+				rc = wlc->cb(fl1h, msg, wlc->cb_data);
 			} else {
 				rc = 0;
 				msgb_free(msg);
@@ -1485,6 +1536,18 @@  static int rx_octphy_msg(struct msgb *msg)
 	return rc;
 }
 
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+	/* configure some reasonable defaults, to be overridden by VTY */
+	plink->u.octphy.rf_port_index = 0;
+	plink->u.octphy.rx_gain_db = 70;
+	plink->u.octphy.tx_atten_db = 0;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+}
+
 /***********************************************************************
  * octphy socket / main loop integration
  ***********************************************************************/
@@ -1534,15 +1597,25 @@  static int octphy_write_cb(struct osmo_fd *fd, struct msgb *msg)
 	return rc;
 }
 
-int l1if_open(struct octphy_hdl *fl1h)
+struct octphy_hdl *l1if_open(struct phy_link *plink)
 {
+	struct octphy_hdl *fl1h;
 	struct ifreq ifr;
 	int sfd, rc;
-	char *phy_dev = fl1h->netdev_name;
+	char *phy_dev = plink->u.octphy.netdev_name;
+
+	fl1h = talloc_zero(plink, struct octphy_hdl);
+	if (!fl1h)
+		return NULL;
+
+	INIT_LLIST_HEAD(&fl1h->wlc_list);
+	INIT_LLIST_HEAD(&fl1h->wlc_postponed);
+	fl1h->phy_link = plink;
 
 	if (!phy_dev) {
 		LOGP(DL1C, LOGL_ERROR, "You have to specify a phy-netdev\n");
-		return -EINVAL;
+		talloc_free(fl1h);
+		return NULL;
 	}
 
 	LOGP(DL1C, LOGL_NOTICE, "Opening L1 interface for OctPHY (%s)\n",
@@ -1553,7 +1626,8 @@  int l1if_open(struct octphy_hdl *fl1h)
 	if (sfd < 0) {
 		LOGP(DL1C, LOGL_FATAL, "Error opening PHY socket: %s\n",
 			strerror(errno));
-		return -EIO;
+		talloc_free(fl1h);
+		return NULL;
 	}
 
 	/* resolve the string device name to an ifindex */
@@ -1564,18 +1638,21 @@  int l1if_open(struct octphy_hdl *fl1h)
 		LOGP(DL1C, LOGL_FATAL, "Error using network device %s: %s\n",
 			phy_dev, strerror(errno));
 		close(sfd);
-		return -EIO;
+		talloc_free(fl1h);
+		return NULL;
 	}
 
 	fl1h->session_id = rand();
 
-	/* set fl1h->phy_addr, which we use as sendto() destionation */
+	/* set fl1h->phy_addr, which we use as sendto() destination */
 	fl1h->phy_addr.sll_family = AF_PACKET;
 	fl1h->phy_addr.sll_protocol = htons(cOCTPKT_HDR_ETHERTYPE);
 	fl1h->phy_addr.sll_ifindex = ifr.ifr_ifindex;
 	fl1h->phy_addr.sll_hatype = ARPHRD_ETHER;
-	fl1h->phy_addr.sll_halen = 6;
-	/* sll_addr is filled by bts_model_vty code */
+	fl1h->phy_addr.sll_halen = ETH_ALEN;
+	/* plink->phy_addr.sll_addr is filled by bts_model_vty code */
+	memcpy(fl1h->phy_addr.sll_addr, plink->u.octphy.phy_addr.sll_addr,
+		ETH_ALEN);
 
 	/* Write queue / osmo_fd registration */
 	osmo_wqueue_init(&fl1h->phy_wq, 10);
@@ -1588,10 +1665,11 @@  int l1if_open(struct octphy_hdl *fl1h)
 	rc = osmo_fd_register(&fl1h->phy_wq.bfd);
 	if (rc < 0) {
 		close(sfd);
-		return -EIO;
+		talloc_free(fl1h);
+		return NULL;
 	}
 
-	return 0;
+	return fl1h;
 }
 
 int l1if_close(struct octphy_hdl *fl1h)
diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h
index 4277865..2dee178 100644
--- a/src/osmo-bts-octphy/l1_if.h
+++ b/src/osmo-bts-octphy/l1_if.h
@@ -13,16 +13,16 @@ 
 #include <osmocom/gsm/protocol/gsm_04_08.h>
 
 #include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
 
 #include <octphy/octvc1/gsm/octvc1_gsm_api.h>
 
 struct octphy_hdl {
+	/* MAC address of the PHY */
+	struct sockaddr_ll phy_addr;
+
 	/* packet socket to talk with PHY */
 	struct osmo_wqueue phy_wq;
-	/* MAC address of th PHY */
-	struct sockaddr_ll phy_addr;
-	/* Network device name */
-	char *netdev_name;
 
 	/* address parameters of the PHY */
 	uint32_t session_id;
@@ -33,12 +33,6 @@  struct octphy_hdl {
 	uint32_t clkmgr_state;
 
 	struct {
-		uint32_t rf_port_index;
-		uint32_t rx_gain_db;
-		uint32_t tx_atten_db;
-	} config;
-
-	struct {
 		struct {
 			char *name;
 			char *description;
@@ -72,8 +66,8 @@  struct octphy_hdl {
 	struct llist_head wlc_postponed;
 	int wlc_postponed_len;
 
-	/* private pointer, points back to TRX */
-	void *priv;
+	/* back pointer to the PHY link */
+	struct phy_link *phy_link;
 
 	struct osmo_timer_list alive_timer;
 	uint32_t alive_prim_cnt;
@@ -82,15 +76,10 @@  struct octphy_hdl {
 	int opened;
 };
 
-static inline struct octphy_hdl *trx_octphy_hdl(struct gsm_bts_trx *trx)
-{
-	return trx->role_bts.l1h;
-}
-
 void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg,
 			struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd);
 
-typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
+typedef int l1if_compl_cb(struct octphy_hdl *fl1, struct msgb *l1_msg, void *data);
 
 /* send a request primitive to the L1 and schedule completion call-back */
 int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg,
@@ -100,19 +89,21 @@  int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg,
 struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx,
 				tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id);
 
-int l1if_open(struct octphy_hdl *fl1h);
+struct octphy_hdl *l1if_open(struct phy_link *plink);
 int l1if_close(struct octphy_hdl *hdl);
 
 int l1if_trx_open(struct gsm_bts_trx *trx);
 int l1if_trx_close_all(struct gsm_bts *bts);
 int l1if_enable_events(struct gsm_bts_trx *trx);
 
-int l1if_activate_rf(struct octphy_hdl *fl1h, int on);
+int l1if_activate_rf(struct gsm_bts_trx *trx, int on);
 
 int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
 		tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *
 		data_ind);
 
+struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id);
+
 struct msgb *l1p_msgb_alloc(void);
 
 /* tch.c */
diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c
index 318c384..c50f1d6 100644
--- a/src/osmo-bts-octphy/l1_oml.c
+++ b/src/osmo-bts-octphy/l1_oml.c
@@ -348,10 +348,11 @@  static void sapi_clear_queue(struct llist_head *queue)
 	}
 }
 
-static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
 {
 	tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *ar =
 		(tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h;
+	struct gsm_bts_trx *trx;
 	struct gsm_lchan *lchan;
 	uint8_t sapi;
 	uint8_t direction;
@@ -361,7 +362,7 @@  static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *
 	 * release it before returning */
 
 	mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar);
-	OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr);
+	trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
 
 	lchan = get_lchan_by_lchid(trx, &ar->LchId);
 	sapi = ar->LchId.bySAPI;
@@ -411,7 +412,8 @@  err:
 
 static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+	struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac;
 
@@ -420,7 +422,7 @@  static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
 	l1if_fill_msg_hdr(&lac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID);
 
-	lac->TrxId.byTrxId = lchan->ts->trx->nr;
+	lac->TrxId.byTrxId = pinst->u.octphy.trx_id;
 	lac->LchId.byTimeslotNb = lchan->ts->nr;
 	lac->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan);
 	lac->LchId.bySAPI = cmd->sapi;
@@ -451,10 +453,11 @@  static tOCTVC1_GSM_CIPHERING_ID_ENUM rsl2l1_ciph[] = {
 	[4] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_3
 };
 
-static int set_ciph_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
 {
 	tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *pcr =
 		(tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *) resp->l2h;
+	struct gsm_bts_trx *trx;
 	struct gsm_bts_trx_ts *ts;
 	struct gsm_lchan *lchan;
 
@@ -470,6 +473,7 @@  static int set_ciph_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d
 		exit(-1);
 	}
 
+	trx = trx_by_l1h(fl1, pcr->TrxId.byTrxId);
 	OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr);
 	ts = &trx->ts[pcr->PchId.byTimeslotNb];
 	/* for some strange reason the response does not tell which
@@ -508,8 +512,8 @@  err:
 
 static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
 {
-	struct gsm_bts_trx *trx = lchan->ts->trx;
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *pcc;
 
@@ -518,6 +522,7 @@  static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c
 	l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID);
 
+	pcc->TrxId.byTrxId = pinst->u.octphy.trx_id;
 	pcc->PchId.byTimeslotNb = lchan->ts->nr;
 	pcc->ulSubchannelNb = lchan_to_GsmL1_SubCh_t(lchan);
 	pcc->ulDirection = cmd->dir;
@@ -627,7 +632,8 @@  static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
 
 static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+	struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
 	int i, res;
 
@@ -654,10 +660,11 @@  static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
 	return res;
 }
 
-static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
 {
 	tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *ldr =
 		(tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h;
+	struct gsm_bts_trx *trx;
 	struct gsm_lchan *lchan;
 	struct sapi_cmd *cmd;
 	uint8_t status;
@@ -666,7 +673,7 @@  static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void
 	 * release it before returning */
 
 	mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr);
-	OSMO_ASSERT(ldr->TrxId.byTrxId == trx->nr);
+	trx = trx_by_l1h(fl1, ldr->TrxId.byTrxId);
 
 	lchan = get_lchan_by_lchid(trx, &ldr->LchId);
 
@@ -725,7 +732,8 @@  err:
 
 static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+	struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *ldc;
 
@@ -734,6 +742,7 @@  static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd
 	l1if_fill_msg_hdr(&ldc->Header, msg, fl1h,cOCTVC1_MSG_TYPE_COMMAND,
 			  cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID);
 
+	ldc->TrxId.byTrxId = pinst->u.octphy.trx_id;
 	ldc->LchId.byTimeslotNb = lchan->ts->nr;
 	ldc->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan);
 	ldc->LchId.byDirection = cmd->dir;
@@ -1031,7 +1040,8 @@  static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
 
 int lchan_activate(struct gsm_lchan *lchan)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+	struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
 	unsigned int i;
 
@@ -1069,7 +1079,7 @@  int l1if_rsl_chan_act(struct gsm_lchan *lchan)
 	return 0;
 }
 
-static int enable_events_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int enable_events_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
 {
 	tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *mser =
 		(tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *) resp->l2h;
@@ -1088,7 +1098,8 @@  static int enable_events_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, vo
 
 int l1if_enable_events(struct gsm_bts_trx *trx)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *mse;
 
@@ -1112,9 +1123,8 @@  int l1if_enable_events(struct gsm_bts_trx *trx)
 		dst = talloc_strdup(ctx, (const char *) src);	\
 	} while (0)
 
-static int app_info_sys_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
 {
-	struct octphy_hdl *fl1h = resp->dst;
 	tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr =
 		(tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *) resp->l2h;
 
@@ -1136,7 +1146,8 @@  static int app_info_sys_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, voi
 
 int l1if_check_app_sys_version(struct gsm_bts_trx *trx)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *ais;
 
@@ -1152,9 +1163,8 @@  int l1if_check_app_sys_version(struct gsm_bts_trx *trx)
 	return l1if_req_compl(fl1h, msg, app_info_sys_compl_cb, 0);
 }
 
-static int app_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
 {
-	struct octphy_hdl *fl1h = resp->dst;
 	tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air =
 		(tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h;
 
@@ -1178,7 +1188,8 @@  static int app_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d
 
 int l1if_check_app_version(struct gsm_bts_trx *trx)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *ai;
 
@@ -1193,9 +1204,50 @@  int l1if_check_app_version(struct gsm_bts_trx *trx)
 	return l1if_req_compl(fl1h, msg, app_info_compl_cb, 0);
 }
 
+static int trx_close_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+	tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *car =
+		(tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *) resp->l2h;
+
+	/* in a completion call-back, we take msgb ownership and must
+	 * release it before returning */
+
+	mOCTVC1_GSM_MSG_TRX_CLOSE_RSP_SWAP(car);
+
+	LOGP(DL1C, LOGL_INFO, "Rx TRX-CLOSE.conf(%u)\n", car->TrxId.byTrxId);
+
+	msgb_free(resp);
+
+	return 0;
+}
+
+static int trx_close(struct gsm_bts_trx *trx)
+{
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct phy_link *plink = pinst->phy_link;
+	struct octphy_hdl *fl1h = plink->u.octphy.hdl;
+	struct msgb *msg = l1p_msgb_alloc();
+	tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *cac;
+
+	cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *)
+				msgb_put(msg, sizeof(*cac));
+	l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+			  cOCTVC1_GSM_MSG_TRX_CLOSE_CID);
+
+	cac->TrxId.byTrxId = pinst->u.octphy.trx_id;
+
+	LOGP(DL1C, LOGL_INFO, "Tx TRX-CLOSE.req(%u)\n", cac->TrxId.byTrxId);
+
+	mOCTVC1_GSM_MSG_TRX_CLOSE_CMD_SWAP(cac);
+
+	return l1if_req_compl(fl1h, msg, trx_close_cb, NULL);
+}
+
 /* call-back once the TRX_OPEN_CID response arrives */
-static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
 {
+	struct gsm_bts_trx *trx;
+
 	tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or =
 		(tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h;
 
@@ -1203,8 +1255,7 @@  static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d
 	 * release it before returning */
 
 	mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or);
-
-	OSMO_ASSERT(or->TrxId.byTrxId == trx->nr);
+	trx = trx_by_l1h(fl1h, or->TrxId.byTrxId);
 
 	LOGP(DL1C, LOGL_INFO, "TRX-OPEN.resp(trx=%u) = %s\n",
 		trx->nr, octvc1_rc2string(or->Header.ulReturnCode));
@@ -1221,7 +1272,6 @@  static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d
 
 	opstart_compl(&trx->mo);
 
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
 	octphy_hw_get_pcb_info(fl1h);
 	octphy_hw_get_rf_port_info(fl1h, 0);
 	octphy_hw_get_rf_ant_rx_config(fl1h, 0, 0);
@@ -1238,22 +1288,24 @@  static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d
 int l1if_trx_open(struct gsm_bts_trx *trx)
 {
 	/* putting it all together */
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct phy_link *plink = pinst->phy_link;
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_GSM_MSG_TRX_OPEN_CMD *oc;
 
 	oc = (tOCTVC1_GSM_MSG_TRX_OPEN_CMD *) msgb_put(msg, sizeof(*oc));
 	l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  cOCTVC1_GSM_MSG_TRX_OPEN_CID);
-	oc->ulRfPortIndex = fl1h->config.rf_port_index;
-	oc->TrxId.byTrxId = trx->nr;
+	oc->ulRfPortIndex = plink->u.octphy.rf_port_index;
+	oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
 	oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn);
 	oc->Config.usArfcn = trx->arfcn;
 	oc->Config.usTsc = trx->bts->bsic & 0x7;
 	oc->Config.usBcchArfcn = trx->bts->c0->arfcn;
-	oc->RfConfig.ulRxGainDb = fl1h->config.rx_gain_db;
+	oc->RfConfig.ulRxGainDb = plink->u.octphy.rx_gain_db;
 	/* FIXME: compute this based on nominal transmit power, etc. */
-	oc->RfConfig.ulTxAttndB = fl1h->config.tx_atten_db;
+	oc->RfConfig.ulTxAttndB = plink->u.octphy.tx_atten_db;
 
 	LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, "
 		"tsc=%u, rx_gain=%u, tx_atten=%u)\n",
@@ -1266,38 +1318,6 @@  int l1if_trx_open(struct gsm_bts_trx *trx)
 	return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL);
 }
 
-static int trx_close_all_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
-{
-	tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car =
-		(tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h;
-
-	/* in a completion call-back, we take msgb ownership and must
-	 * release it before returning */
-
-	mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car);
-
-	msgb_free(resp);
-
-	return 0;
-}
-
-int l1if_trx_close_all(struct gsm_bts *bts)
-{
-	struct octphy_hdl *fl1h = trx_octphy_hdl(bts->c0);
-	struct msgb *msg = l1p_msgb_alloc();
-	tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac;
-
-	cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *)
-				msgb_put(msg, sizeof(*cac));
-	l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
-			  cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID);
-
-	mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac);
-
-	return l1if_req_compl(fl1h, msg, trx_close_all_cb, NULL);
-}
-
-
 uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx)
 {
 	return 0;
@@ -1313,8 +1333,6 @@  static int trx_init(struct gsm_bts_trx *trx)
 		/* return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); */
 	}
 
-	l1if_trx_close_all(trx->bts);
-
 	l1if_check_app_version(trx);
 	l1if_check_app_sys_version(trx);
 
@@ -1325,11 +1343,12 @@  static int trx_init(struct gsm_bts_trx *trx)
  * PHYSICAL CHANNE ACTIVATION
  ***********************************************************************/
 
-static int pchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
 {
 	tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar =
 		(tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h;
 	uint8_t ts_nr;
+	struct gsm_bts_trx *trx;
 	struct gsm_bts_trx_ts *ts;
 	struct gsm_abis_mo *mo;
 
@@ -1337,9 +1356,8 @@  static int pchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *
 	 * release it before returning */
 
 	mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar);
+	trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
 	ts_nr = ar->PchId.byTimeslotNb;
-
-	OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr);
 	OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts));
 
 	ts = &trx->ts[ts_nr];
@@ -1367,7 +1385,8 @@  static int pchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *
 
 static int ts_connect(struct gsm_bts_trx_ts *ts)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(ts->trx);
+	struct phy_instance *pinst = trx_phy_instance(ts->trx);
+	struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
 	struct msgb *msg = l1p_msgb_alloc();
 	tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc =
 		(tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc;
@@ -1376,7 +1395,7 @@  static int ts_connect(struct gsm_bts_trx_ts *ts)
 	l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
 			  cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID);
 
-	oc->TrxId.byTrxId = ts->trx->nr;
+	oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
 	oc->PchId.byTimeslotNb = ts->nr;
 	oc->ulChannelType = pchan_to_logChComb[ts->pchan];
 
@@ -1430,8 +1449,7 @@  int bts_model_oml_estab(struct gsm_bts *bts)
 	int i;
 	for (i = 0; i < bts->num_trx; i++) {
 		struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i);
-		struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
-		l1if_activate_rf(fl1h, 1);
+		l1if_activate_rf(trx, 1);
 	}
 	return 0;
 }
@@ -1447,14 +1465,13 @@  int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
 
 int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
 {
-	struct octphy_hdl *fl1 = trx_octphy_hdl(trx);
-	return l1if_activate_rf(fl1, 0);
+	return l1if_activate_rf(trx, 0);
 }
 
 int bts_model_trx_close(struct gsm_bts_trx *trx)
 {
 	/* FIXME: close only one TRX */
-	return l1if_trx_close_all(trx->bts);
+	return trx_close(trx);
 }
 
 
diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c
index 1f516e4..0f4d0dd 100644
--- a/src/osmo-bts-octphy/main.c
+++ b/src/osmo-bts-octphy/main.c
@@ -52,10 +52,9 @@ 
 
 extern int pcu_direct;
 
-static struct gsm_bts *bts;
-
 int bts_model_print_help()
 {
+	return 0;
 }
 
 int bts_model_handle_options(int argc, char **argv)
diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c
index 5291742..ec7c4be 100644
--- a/src/osmo-bts-octphy/octphy_hw_api.c
+++ b/src/osmo-bts-octphy/octphy_hw_api.c
@@ -35,7 +35,7 @@ 
 #include <octphy/octvc1/hw/octvc1_hw_api_swap.h>
 
 /* Chapter 12.1 */
-static int get_pcb_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int get_pcb_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
 {
 	tOCTVC1_HW_MSG_PCB_INFO_RSP *pir =
 		(tOCTVC1_HW_MSG_PCB_INFO_RSP *) resp->l2h;
@@ -69,7 +69,7 @@  int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h)
 }
 
 /* Chapter 12.9 */
-static int rf_port_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+static int rf_port_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
 				 void *data)
 {
 	tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *pir =
@@ -114,7 +114,7 @@  static const struct value_string radio_std_vals[] = {
 };
 
 /* Chapter 12.10 */
-static int rf_port_stats_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+static int rf_port_stats_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
 				  void *data)
 {
 	tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr =
@@ -167,7 +167,7 @@  static const struct value_string rx_gain_mode_vals[] = {
 };
 
 /* Chapter 12.13 */
-static int rf_ant_rx_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+static int rf_ant_rx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
 				void *data)
 {
 	tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *arc =
@@ -209,7 +209,7 @@  int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx,
 }
 
 /* Chapter 12.14 */
-static int rf_ant_tx_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+static int rf_ant_tx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
 				void *data)
 {
 	tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *atc =
@@ -292,7 +292,7 @@  static const struct value_string clocksync_state_vals[] = {
 };
 
 /* Chapter 12.15 */
-static int get_clock_sync_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+static int get_clock_sync_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
 				   void *data)
 {
 	tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *cir =
@@ -326,7 +326,7 @@  int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h)
 }
 
 /* Chapter 12.16 */
-static int get_clock_sync_stats_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+static int get_clock_sync_stats_cb(struct octphy_hdl *fl1, struct msgb *resp,
 				   void *data)
 {
 	tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr =
diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c
index dbc903a..6e58ad4 100644
--- a/src/osmo-bts-octphy/octphy_vty.c
+++ b/src/osmo-bts-octphy/octphy_vty.c
@@ -38,6 +38,7 @@ 
 #include <osmocom/vty/misc.h>
 
 #include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
 #include <osmo-bts/logging.h>
 #include <osmo-bts/vty.h>
 
@@ -51,149 +52,189 @@ 
 	SHOW_STR				\
 	TRX_STR
 
+#define OCT_STR	"OCTPHY Um interface\n"
+
 static struct gsm_bts *vty_bts;
 
 /* configuration */
 
-DEFUN(cfg_bts_phy_hwaddr, cfg_bts_phy_hwaddr_cmd,
-	"phy-hw-addr HWADDR",
-	"Configure the hardware addess of the OCTPHY\n"
+DEFUN(cfg_phy_hwaddr, cfg_phy_hwaddr_cmd,
+	"octphy hw-addr HWADDR",
+	OCT_STR "Configure the hardware addess of the OCTPHY\n"
 	"hardware address in aa:bb:cc:dd:ee:ff format\n")
 {
-	struct gsm_bts *bts = vty->index;
-	struct gsm_bts_trx *trx = bts->c0;
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_link *plink = vty->index;
 	int rc;
 
-	rc = osmo_macaddr_parse(fl1h->phy_addr.sll_addr, argv[0]);
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = osmo_macaddr_parse(plink->u.octphy.phy_addr.sll_addr, argv[0]);
 	if (rc < 0)
 		return CMD_WARNING;
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_bts_phy_netdev, cfg_bts_phy_netdev_cmd,
-	"phy-netdev NAME",
-	"Configure the hardware device towards the OCTPHY\n"
+DEFUN(cfg_phy_netdev, cfg_phy_netdev_cmd,
+	"octphy net-device NAME",
+	OCT_STR "Configure the hardware device towards the OCTPHY\n"
 	"Ethernet device name\n")
 {
-	struct gsm_bts *bts = vty->index;
-	struct gsm_bts_trx *trx = bts->c0;
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_link *plink = vty->index;
 
-	if (fl1h->netdev_name)
-		talloc_free(fl1h->netdev_name);
-	fl1h->netdev_name = talloc_strdup(fl1h, argv[0]);
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (plink->u.octphy.netdev_name)
+		talloc_free(plink->u.octphy.netdev_name);
+	plink->u.octphy.netdev_name = talloc_strdup(plink, argv[0]);
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_rf_port_idx, cfg_trx_rf_port_idx_cmd,
-	"rf-port-index <0-255>",
-	"Configure the RF Port for this TRX\n"
+DEFUN(cfg_phy_rf_port_idx, cfg_phy_rf_port_idx_cmd,
+	"octphy rf-port-index <0-255>",
+	OCT_STR "Configure the RF Port for this TRX\n"
 	"RF Port Index\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_link *plink = vty->index;
 
-	fl1h->config.rf_port_index = atoi(argv[0]);
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	plink->u.octphy.rf_port_index = atoi(argv[0]);
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_rx_gain_db, cfg_trx_rx_gain_db_cmd,
-	"rx-gain <0-73>",
-	"Configure the Rx Gain in dB\n"
+DEFUN(cfg_phy_rx_gain_db, cfg_phy_rx_gain_db_cmd,
+	"octphy rx-gain <0-73>",
+	OCT_STR "Configure the Rx Gain in dB\n"
 	"Rx gain in dB\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
 
-	fl1h->config.rx_gain_db = atoi(argv[0]);
+	plink->u.octphy.rx_gain_db = atoi(argv[0]);
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_tx_atten_db, cfg_trx_tx_atten_db_cmd,
-	"tx-attenuation <0-359>",
-	"Configure the Tx Attenuation in quarter-dB\n"
+DEFUN(cfg_phy_tx_atten_db, cfg_phy_tx_atten_db_cmd,
+	"octphy tx-attenuation <0-359>",
+	OCT_STR "Configure the Tx Attenuation in quarter-dB\n"
 	"Tx attenuation in quarter-dB\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
 
-	fl1h->config.tx_atten_db = atoi(argv[0]);
+	plink->u.octphy.tx_atten_db = atoi(argv[0]);
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(get_rf_port_stats, get_rf_port_stats_cmd,
-	"get-rf-port-stats <0-1>",
-	"Obtain statistics for the RF Port\n"
+DEFUN(show_rf_port_stats, show_rf_port_stats_cmd,
+	"show phy <0-255> rf-port-stats <0-1>",
+	"Show statistics for the RF Port\n"
 	"RF Port Number\n")
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(vty_bts->c0);
+	int phy_nr = atoi(argv[0]);
+	struct phy_link *plink = phy_link_by_num(phy_nr);
 
-	octphy_hw_get_rf_port_stats(fl1h, atoi(argv[0]));
+	octphy_hw_get_rf_port_stats(plink->u.octphy.hdl, atoi(argv[1]));
+
+	/* FIXME: Actually print to VTY, not just log */
+	vty_out(vty, "Please check the log file for the response%s",
+		VTY_NEWLINE);
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(get_clk_sync_stats, get_clk_sync_stats_cmd,
-	"get-clk-sync-stats",
+DEFUN(show_clk_sync_stats, show_clk_sync_stats_cmd,
+	"show phy <0-255> clk-sync-stats",
 	"Obtain statistics for the Clock Sync Manager\n")
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(vty_bts->c0);
+	int phy_nr = atoi(argv[0]);
+	struct phy_link *plink = phy_link_by_num(phy_nr);
+
+	octphy_hw_get_clock_sync_stats(plink->u.octphy.hdl);
 
-	octphy_hw_get_clock_sync_stats(fl1h);
+	/* FIXME: Actually print to VTY, not just log */
+	vty_out(vty, "Please check the log file for the response%s",
+		VTY_NEWLINE);
 
 	return CMD_SUCCESS;
 }
 
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+	if (plink->u.octphy.netdev_name)
+		vty_out(vty, " netdev %s%s", plink->u.octphy.netdev_name,
+			VTY_NEWLINE);
+
+	vty_out(vty, " hw-addr %02x:%02x:%02x:%02x:%02x:%02x%s",
+		plink->u.octphy.phy_addr.sll_addr[0],
+		plink->u.octphy.phy_addr.sll_addr[1],
+		plink->u.octphy.phy_addr.sll_addr[2],
+		plink->u.octphy.phy_addr.sll_addr[3],
+		plink->u.octphy.phy_addr.sll_addr[4],
+		plink->u.octphy.phy_addr.sll_addr[5],
+		VTY_NEWLINE);
+	vty_out(vty, "  rx-gain %u%s", plink->u.octphy.rx_gain_db,
+		VTY_NEWLINE);
+	vty_out(vty, "  tx-attenuation %u%s", plink->u.octphy.tx_atten_db,
+		VTY_NEWLINE);
+	vty_out(vty, "  rf-port-index %u%s", plink->u.octphy.rf_port_index,
+		VTY_NEWLINE);
+}
+
 void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
 {
 	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
-	struct octphy_hdl *fl1h = trx_octphy_hdl(bts->c0);
-
-	if (fl1h->netdev_name)
-		vty_out(vty, " phy-netdev %s%s", fl1h->netdev_name,
-			VTY_NEWLINE);
 
 	if (btsb->auto_band)
 		vty_out(vty, " auto-band%s", VTY_NEWLINE);
 
-	vty_out(vty, " phy-hw-addr %02x:%02x:%02x:%02x:%02x:%02x%s",
-		fl1h->phy_addr.sll_addr[0], fl1h->phy_addr.sll_addr[1],
-		fl1h->phy_addr.sll_addr[2], fl1h->phy_addr.sll_addr[3],
-		fl1h->phy_addr.sll_addr[4], fl1h->phy_addr.sll_addr[5],
-		VTY_NEWLINE);
 }
 
 void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
 {
-	struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
-
-	vty_out(vty, "  rx-gain %u%s", fl1h->config.rx_gain_db,
-		VTY_NEWLINE);
-	vty_out(vty, "  tx-attenuation %u%s", fl1h->config.tx_atten_db,
-		VTY_NEWLINE);
 }
 
 DEFUN(show_sys_info, show_sys_info_cmd,
-	"show trx <0-255> system-information",
+	"show phy <0-255> system-information",
 	SHOW_TRX_STR "Display information about system\n")
 {
-	int trx_nr = atoi(argv[0]);
-	struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+	int phy_nr = atoi(argv[0]);
+	struct phy_link *plink = phy_link_by_num(phy_nr);
 	struct octphy_hdl *fl1h;
-	int i;
 
-	if (!trx) {
-		vty_out(vty, "Cannot find TRX number %u%s",
-			trx_nr, VTY_NEWLINE);
+	if (!plink) {
+		vty_out(vty, "Cannot find PHY number %u%s",
+			phy_nr, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
-	fl1h = trx_octphy_hdl(trx);
+	fl1h = plink->u.octphy.hdl;
 
 	vty_out(vty, "System Platform: '%s', Version: '%s'%s",
 		fl1h->info.system.platform, fl1h->info.system.version,
@@ -210,15 +251,14 @@  int bts_model_vty_init(struct gsm_bts *bts)
 {
 	vty_bts = bts;
 
-	install_element(BTS_NODE, &cfg_bts_phy_hwaddr_cmd);
-	install_element(BTS_NODE, &cfg_bts_phy_netdev_cmd);
+	install_element(PHY_NODE, &cfg_phy_hwaddr_cmd);
+	install_element(PHY_NODE, &cfg_phy_netdev_cmd);
+	install_element(PHY_NODE, &cfg_phy_rf_port_idx_cmd);
+	install_element(PHY_NODE, &cfg_phy_rx_gain_db_cmd);
+	install_element(PHY_NODE, &cfg_phy_tx_atten_db_cmd);
 
-	install_element(TRX_NODE, &cfg_trx_rf_port_idx_cmd);
-	install_element(TRX_NODE, &cfg_trx_rx_gain_db_cmd);
-	install_element(TRX_NODE, &cfg_trx_tx_atten_db_cmd);
-
-	install_element_ve(&get_rf_port_stats_cmd);
-	install_element_ve(&get_clk_sync_stats_cmd);
+	install_element_ve(&show_rf_port_stats_cmd);
+	install_element_ve(&show_clk_sync_stats_cmd);
 	install_element_ve(&show_sys_info_cmd);
 
 	return 0;
@@ -226,13 +266,5 @@  int bts_model_vty_init(struct gsm_bts *bts)
 
 int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
 {
-	/* FIXME: really ugly hack: We can only initialize the L1 intrface
-	 * after reading the config file, and this is the only call-back after
-	 * vty_read_config_fioe() at this point.  Will be cleaned up with the
-	 * phy interface generalization patches coming up soon as part of the
-	 * multi-trx work */
-	struct octphy_hdl *fl1h = bts->c0->role_bts.l1h;
-	l1if_open(fl1h);
-
 	return 0;
 }
diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c
index edd4b7b..befaffd 100644
--- a/src/osmo-bts-trx/l1_if.c
+++ b/src/osmo-bts-trx/l1_if.c
@@ -59,7 +59,7 @@  static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = {
  * create/destroy trx l1 instance
  */
 
-struct trx_l1h *l1if_open(struct gsm_bts_trx *trx)
+struct trx_l1h *l1if_open(struct phy_instance *pinst)
 {
 	struct trx_l1h *l1h;
 	int rc;
@@ -67,11 +67,9 @@  struct trx_l1h *l1if_open(struct gsm_bts_trx *trx)
 	l1h = talloc_zero(tall_bts_ctx, struct trx_l1h);
 	if (!l1h)
 		return NULL;
-	l1h->trx = trx;
-	l1h->l1s.trx = trx;
-	trx->role_bts.l1h = l1h;
+	l1h->phy_inst = pinst;
 
-	trx_sched_init(&l1h->l1s);
+	trx_sched_init(&l1h->l1s, pinst->trx);
 
 	rc = trx_if_open(l1h);
 	if (rc < 0) {
@@ -83,7 +81,6 @@  struct trx_l1h *l1if_open(struct gsm_bts_trx *trx)
 
 err:
 	l1if_close(l1h);
-	trx->role_bts.l1h = NULL;
 	return NULL;
 }
 
@@ -100,7 +97,8 @@  void l1if_reset(struct trx_l1h *l1h)
 
 static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail)
 {
-	struct gsm_bts_trx *trx = l1h->trx;
+	struct phy_instance *pinst = l1h->phy_inst;
+	struct gsm_bts_trx *trx = pinst->trx;
 	uint8_t tn;
 
 	/* HACK, we should change state when we receive first clock from
@@ -132,10 +130,10 @@  static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail)
 int check_transceiver_availability(struct gsm_bts *bts, int avail)
 {
 	struct gsm_bts_trx *trx;
-	struct trx_l1h *l1h;
 
 	llist_for_each_entry(trx, &bts->trx_list, list) {
-		l1h = trx_l1h_hdl(trx);
+		struct phy_instance *pinst = trx_phy_instance(trx);
+		struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 		check_transceiver_availability_trx(l1h, avail);
 	}
 	return 0;
@@ -147,6 +145,7 @@  int check_transceiver_availability(struct gsm_bts *bts, int avail)
  */
 int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
 {
+	struct phy_link *plink = l1h->phy_inst->phy_link;
 	uint8_t tn;
 
 	if (!transceiver_available)
@@ -177,18 +176,23 @@  int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
 		}
 
 		/* after power on */
-		if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) {
-			trx_if_cmd_setrxgain(l1h, l1h->config.rxgain);
-			l1h->config.rxgain_sent = 1;
-		}
-		if (l1h->config.power_valid && !l1h->config.power_sent) {
-			trx_if_cmd_setpower(l1h, l1h->config.power);
-			l1h->config.power_sent = 1;
+		if (l1h->phy_inst->num == 0) {
+			if (plink->u.osmotrx.rxgain_valid &&
+			    !plink->u.osmotrx.rxgain_sent) {
+				trx_if_cmd_setrxgain(l1h, plink->u.osmotrx.rxgain);
+				plink->u.osmotrx.rxgain_sent = 1;
+			}
+			if (plink->u.osmotrx.power_valid &&
+			    !plink->u.osmotrx.power_sent) {
+				trx_if_cmd_setpower(l1h, plink->u.osmotrx.power);
+				plink->u.osmotrx.power_sent = 1;
+			}
 		}
 		if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) {
 			trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly);
 			l1h->config.maxdly_sent = 1;
 		}
+
 		for (tn = 0; tn < TRX_NR_TS; tn++) {
 			if (l1h->config.slottype_valid[tn]
 			 && !l1h->config.slottype_sent[tn]) {
@@ -203,8 +207,10 @@  int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
 	if (!l1h->config.poweron && !l1h->config.poweron_sent) {
 		trx_if_cmd_poweroff(l1h);
 		l1h->config.poweron_sent = 1;
-		l1h->config.rxgain_sent = 0;
-		l1h->config.power_sent = 0;
+		if (l1h->phy_inst->num == 0) {
+			plink->u.osmotrx.rxgain_sent = 0;
+			plink->u.osmotrx.power_sent = 0;
+		}
 		l1h->config.maxdly_sent = 0;
 		for (tn = 0; tn < TRX_NR_TS; tn++)
 			l1h->config.slottype_sent[tn] = 0;
@@ -216,17 +222,20 @@  int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
 int l1if_provision_transceiver(struct gsm_bts *bts)
 {
 	struct gsm_bts_trx *trx;
-	struct trx_l1h *l1h;
 	uint8_t tn;
 
 	llist_for_each_entry(trx, &bts->trx_list, list) {
-		l1h = trx_l1h_hdl(trx);
+		struct phy_instance *pinst = trx_phy_instance(trx);
+		struct phy_link *plink = pinst->phy_link;
+		struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 		l1h->config.arfcn_sent = 0;
 		l1h->config.tsc_sent = 0;
 		l1h->config.bsic_sent = 0;
 		l1h->config.poweron_sent = 0;
-		l1h->config.rxgain_sent = 0;
-		l1h->config.power_sent = 0;
+		if (l1h->phy_inst->num == 0) {
+			plink->u.osmotrx.rxgain_sent = 0;
+			plink->u.osmotrx.power_sent = 0;
+		}
 		l1h->config.maxdly_sent = 0;
 		for (tn = 0; tn < TRX_NR_TS; tn++)
 			l1h->config.slottype_sent[tn] = 0;
@@ -242,7 +251,8 @@  int l1if_provision_transceiver(struct gsm_bts *bts)
 /* initialize the layer1 */
 static int trx_init(struct gsm_bts_trx *trx)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 
 	/* power on transceiver, if not already */
 	if (!l1h->config.poweron) {
@@ -264,7 +274,8 @@  static int trx_init(struct gsm_bts_trx *trx)
 /* deactivate transceiver */
 int bts_model_trx_close(struct gsm_bts_trx *trx)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 	enum gsm_phys_chan_config pchan = trx->ts[0].pchan;
 
 	/* close all logical channels and reset timeslots */
@@ -308,7 +319,6 @@  int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
 static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr)
 {
 	struct gsm_bts_trx *trx;
-	struct trx_l1h *l1h;
 	uint8_t bsic = bts->bsic;
 	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
 
@@ -318,7 +328,8 @@  static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr)
 	}
 
 	llist_for_each_entry(trx, &bts->trx_list, list) {
-		l1h = trx_l1h_hdl(trx);
+		struct phy_instance *pinst = trx_phy_instance(trx);
+		struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 		if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) {
 			l1h->config.bsic = bsic;
 			l1h->config.bsic_valid = 1;
@@ -335,7 +346,9 @@  static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr)
 /* set trx attributes */
 static uint8_t trx_set_trx(struct gsm_bts_trx *trx)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct phy_link *plink = pinst->phy_link;
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 	uint16_t arfcn = trx->arfcn;
 
 	if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) {
@@ -345,10 +358,10 @@  static uint8_t trx_set_trx(struct gsm_bts_trx *trx)
 		l1if_provision_transceiver_trx(l1h);
 	}
 
-	if (l1h->config.power_oml) {
-		l1h->config.power = trx->max_power_red;
-		l1h->config.power_valid = 1;
-		l1h->config.power_sent = 0;
+	if (plink->u.osmotrx.power_oml && pinst->num == 0) {
+		plink->u.osmotrx.power = trx->max_power_red;
+		plink->u.osmotrx.power_valid = 1;
+		plink->u.osmotrx.power_sent = 0;
 		l1if_provision_transceiver_trx(l1h);
 	}
 
@@ -358,7 +371,8 @@  static uint8_t trx_set_trx(struct gsm_bts_trx *trx)
 /* set ts attributes */
 static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(ts->trx);
+	struct phy_instance *pinst = trx_phy_instance(ts->trx);
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 	uint8_t tn = ts->nr;
 	uint16_t tsc = ts->tsc;
 	enum gsm_phys_chan_config pchan = ts->pchan;
@@ -439,6 +453,7 @@  static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan,
 static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr,
 	enum osmo_mph_info_type type, uint8_t cause)
 {
+	struct phy_instance *pinst = l1h->phy_inst;
 	struct osmo_phsap_prim l1sap;
 
 	memset(&l1sap, 0, sizeof(l1sap));
@@ -448,7 +463,7 @@  static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr,
 	l1sap.u.info.u.act_cnf.chan_nr = chan_nr;
 	l1sap.u.info.u.act_cnf.cause = cause;
 
-	return l1sap_up(l1h->trx, &l1sap);
+	return l1sap_up(pinst->trx, &l1sap);
 }
 
 int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn)
@@ -503,7 +518,8 @@  int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint
 /* primitive from common part */
 int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 	struct msgb *msg = l1sap->oph.msg;
 	uint8_t chan_nr;
 	uint8_t tn, ss;
diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h
index f492687..187303c 100644
--- a/src/osmo-bts-trx/l1_if.h
+++ b/src/osmo-bts-trx/l1_if.h
@@ -2,6 +2,7 @@ 
 #define L1_IF_H_TRX
 
 #include <osmo-bts/scheduler.h>
+#include <osmo-bts/phy_link.h>
 
 struct trx_config {
 	uint8_t			poweron;	/* poweron(1) or poweroff(0) */
@@ -19,15 +20,6 @@  struct trx_config {
 	uint8_t			bsic;
 	int			bsic_sent;
 
-	int			rxgain_valid;
-	int			rxgain;
-	int			rxgain_sent;
-
-	int			power_valid;
-	int			power;
-	int			power_oml;
-	int			power_sent;
-
 	int			maxdly_valid;
 	int			maxdly;
 	int			maxdly_sent;
@@ -42,7 +34,8 @@  struct trx_config {
 struct trx_l1h {
 	struct llist_head	trx_ctrl_list;
 
-	struct gsm_bts_trx	*trx;
+	//struct gsm_bts_trx	*trx;
+	struct phy_instance	*phy_inst;
 
 	struct osmo_fd		trx_ofd_ctrl;
 	struct osmo_timer_list	trx_ctrl_timer;
@@ -55,7 +48,7 @@  struct trx_l1h {
 	struct l1sched_trx	l1s;
 };
 
-struct trx_l1h *l1if_open(struct gsm_bts_trx *trx);
+struct trx_l1h *l1if_open(struct phy_instance *pinst);
 void l1if_close(struct trx_l1h *l1h);
 void l1if_reset(struct trx_l1h *l1h);
 int check_transceiver_availability(struct gsm_bts *bts, int avail);
@@ -69,7 +62,8 @@  int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint
 
 static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = trx->role_bts.l1h;
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 	return &l1h->l1s;
 }
 
diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c
index aa6987c..62e8fe9 100644
--- a/src/osmo-bts-trx/main.c
+++ b/src/osmo-bts-trx/main.c
@@ -45,6 +45,7 @@ 
 #include <osmocom/core/bits.h>
 
 #include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
 #include <osmo-bts/logging.h>
 #include <osmo-bts/abis.h>
 #include <osmo-bts/bts.h>
@@ -58,43 +59,6 @@ 
 #include "l1_if.h"
 #include "trx_if.h"
 
-int bts_model_init(struct gsm_bts *bts)
-{
-	void *l1h;
-	struct gsm_bts_trx *trx;
-	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
-
-	btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2);
-	if (!settsc_enabled && !setbsic_enabled)
-		settsc_enabled = setbsic_enabled = 1;
-
-	llist_for_each_entry(trx, &bts->trx_list, list) {
-		l1h = l1if_open(trx);
-		if (!l1h) {
-			LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n");
-			goto error;
-		}
-
-		trx->role_bts.l1h = l1h;
-		trx->nominal_power = 23;
-
-		l1if_reset(l1h);
-	}
-
-	bts_model_vty_init(bts);
-
-	return 0;
-
-error:
-	llist_for_each_entry(trx, &bts->trx_list, list) {
-		l1h = trx->role_bts.l1h;
-		if (l1h)
-			l1if_close(l1h);
-	}
-
-	return -EIO;
-}
-
 /* dummy, since no direct dsp support */
 uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
 {
@@ -103,10 +67,6 @@  uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
 
 void bts_model_print_help()
 {
-	printf(
-		"  -I	--local-trx-ip	Local IP for transceiver to connect (default=%s)\n"
-		, transceiver_ip
-		);
 }
 
 int bts_model_handle_options(int argc, char **argv)
@@ -116,21 +76,16 @@  int bts_model_handle_options(int argc, char **argv)
 	while (1) {
 		int option_idx = 0, c;
 		static const struct option long_options[] = {
-			/* specific to this hardware */
-			{ "local-trx-ip", 1, 0, 'I' },
 			{ 0, 0, 0, 0 }
 		};
 
-		c = getopt_long(argc, argv, "I:",
+		c = getopt_long(argc, argv, "",
 				long_options, &option_idx);
 
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case 'I':
-			transceiver_ip = strdup(optarg);
-			break;
 		default:
 			num_errors++;
 			break;
@@ -140,6 +95,35 @@  int bts_model_handle_options(int argc, char **argv)
 	return num_errors;
 }
 
+int bts_model_init(struct gsm_bts *bts)
+{
+	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+	btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2);
+
+	/* FIXME: this needs to be overridden with the real hardrware
+	 * value */
+	bts->c0->nominal_power = 23;
+
+	bts_model_vty_init(bts);
+
+	return 0;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+	plink->u.osmotrx.transceiver_ip = talloc_strdup(plink, "127.0.0.1");
+	plink->u.osmotrx.base_port_local = 5800;
+	plink->u.osmotrx.base_port_remote = 5700;
+	plink->u.osmotrx.clock_advance = 20;
+	plink->u.osmotrx.rts_advance = 5;
+	plink->u.osmotrx.power_oml = 1;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+}
+
 int main(int argc, char **argv)
 {
 	return bts_main(argc, argv);
diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c
index ac9212b..15c05e8 100644
--- a/src/osmo-bts-trx/scheduler_trx.c
+++ b/src/osmo-bts-trx/scheduler_trx.c
@@ -55,12 +55,6 @@  uint32_t transceiver_last_fn;
 static struct timeval transceiver_clock_tv;
 static struct osmo_timer_list transceiver_clock_timer;
 
-/* clock advance for the transceiver */
-uint32_t trx_clock_advance = 20;
-
-/* advance RTS to give some time for data processing. (especially PCU) */
-uint32_t trx_rts_advance = 5; /* about 20ms */
-
 /* Enable this to multiply TOA of RACH by 10.
  * This is usefull to check tenth of timing advances with RSSI test tool.
  * Note that regular phones will not work when using this test! */
@@ -1262,14 +1256,16 @@  static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn)
 	/* send time indication */
 	l1if_mph_time_ind(bts, fn);
 
-	/* advance frame number, so the transceiver has more time until
-	 * it must be transmitted. */
-	fn = (fn + trx_clock_advance) % GSM_HYPERFRAME;
-
 	/* process every TRX */
 	llist_for_each_entry(trx, &bts->trx_list, list) {
-		struct trx_l1h *l1h = trx_l1h_hdl(trx);
-		struct l1sched_trx *l1t = trx_l1sched_hdl(trx);
+		struct phy_instance *pinst = trx_phy_instance(trx);
+		struct phy_link *plink = pinst->phy_link;
+		struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+		struct l1sched_trx *l1t = &l1h->l1s;
+
+		/* advance frame number, so the transceiver has more
+		 * time until it must be transmitted. */
+		fn = (fn + plink->u.osmotrx.clock_advance) % GSM_HYPERFRAME;
 
 		/* we don't schedule, if power is off */
 		if (!trx_if_powered(l1h))
@@ -1279,7 +1275,7 @@  static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn)
 		for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
 			/* ready-to-send */
 			_sched_rts(l1t, tn,
-				(fn + trx_rts_advance) % GSM_HYPERFRAME);
+				(fn + plink->u.osmotrx.rts_advance) % GSM_HYPERFRAME);
 			/* get burst for FN */
 			bits = _sched_dl_burst(l1t, tn, fn);
 			if (!bits) {
@@ -1323,10 +1319,12 @@  no_clock:
 		/* flush pending messages of transceiver */
 		/* close all logical channels and reset timeslots */
 		llist_for_each_entry(trx, &bts->trx_list, list) {
-			trx_if_flush(trx_l1h_hdl(trx));
-			trx_sched_reset(trx_l1sched_hdl(trx));
+			struct phy_instance *pinst = trx_phy_instance(trx);
+			struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+			trx_if_flush(l1h);
+			trx_sched_reset(&l1h->l1s);
 			if (trx->nr == 0)
-				trx_if_cmd_poweroff(trx_l1h_hdl(trx));
+				trx_if_cmd_poweroff(l1h);
 		}
 
 		/* tell BSC */
@@ -1461,7 +1459,8 @@  new_clock:
 
 void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(l1t->trx);
+	struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 
 	if (activate)
 		trx_if_cmd_handover(l1h, tn, ss);
diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c
index dbe6f68..a4c16dc 100644
--- a/src/osmo-bts-trx/trx_if.c
+++ b/src/osmo-bts-trx/trx_if.c
@@ -2,6 +2,7 @@ 
  * OpenBTS TRX interface handling
  *
  * Copyright (C) 2013  Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2016  Harald Welte <laforge@gnumonks.org>
  *
  * All Rights Reserved
  *
@@ -34,6 +35,7 @@ 
 #include <osmocom/core/talloc.h>
 #include <osmocom/core/bits.h>
 
+#include <osmo-bts/phy_link.h>
 #include <osmo-bts/logging.h>
 #include <osmo-bts/bts.h>
 #include <osmo-bts/scheduler.h>
@@ -45,7 +47,6 @@ 
 //#define TOA_RSSI_DEBUG
 
 int transceiver_available = 0;
-const char *transceiver_ip = "127.0.0.1";
 int settsc_enabled = 0;
 int setbsic_enabled = 0;
 
@@ -53,11 +54,10 @@  int setbsic_enabled = 0;
  * socket
  */
 
-static uint16_t base_port_local = 5800;
-
 /* open socket */
-static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port,
-	int (*cb)(struct osmo_fd *fd, unsigned int what))
+static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host,
+			uint16_t port_local, uint16_t port_remote,
+			int (*cb)(struct osmo_fd *fd, unsigned int what))
 {
 	struct sockaddr_storage sas;
 	struct sockaddr *sa = (struct sockaddr *)&sas;
@@ -71,8 +71,8 @@  static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port,
 	ofd->data = priv;
 
 	/* Listen / Binds */
-	rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, transceiver_ip,
-		port, OSMO_SOCK_F_BIND);
+	rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host,
+		port_local, OSMO_SOCK_F_BIND);
 	if (rc < 0)
 		return rc;
 
@@ -84,10 +84,10 @@  static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port,
 
 	if (sa->sa_family == AF_INET) {
 		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
-		sin->sin_port = htons(ntohs(sin->sin_port) - 100);
+		sin->sin_port = htons(port_remote);
 	} else if (sa->sa_family == AF_INET6) {
 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
-		sin6->sin6_port = htons(ntohs(sin6->sin6_port) - 100);
+		sin6->sin6_port = htons(port_remote);
 	} else {
 		return -EINVAL;
 	}
@@ -96,7 +96,6 @@  static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port,
 	if (rc)
 		return rc;
 
-	
 	return 0;
 }
 
@@ -115,13 +114,11 @@  static void trx_udp_close(struct osmo_fd *ofd)
  * clock
  */
 
-static struct osmo_fd trx_ofd_clk;
-
-
 /* get clock from clock socket */
 static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
 {
-	struct trx_l1h *l1h = ofd->data;
+	struct phy_link *plink = ofd->data;
+	struct phy_instance *pinst = phy_instance_by_num(plink, 0);
 	char buf[1500];
 	int len;
 	uint32_t fn;
@@ -146,7 +143,7 @@  static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
 			"correctly, correcting to fn=%u\n", fn);
 	}
 
-	trx_sched_clock(l1h->trx->bts, fn);
+	trx_sched_clock(pinst->trx->bts, fn);
 
 	return 0;
 }
@@ -168,8 +165,8 @@  static void trx_ctrl_send(struct trx_l1h *l1h)
 		return;
 	tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list);
 
-	LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to trx=%u\n", tcm->cmd,
-		l1h->trx->nr);
+	LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to %s\n", tcm->cmd,
+		phy_instance_name(l1h->phy_inst));
 	/* send command */
 	send(l1h->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd)+1, 0);
 
@@ -184,8 +181,8 @@  static void trx_ctrl_timer_cb(void *data)
 {
 	struct trx_l1h *l1h = data;
 
-	LOGP(DTRX, LOGL_NOTICE, "No response from transceiver for trx=%d\n",
-		l1h->trx->nr);
+	LOGP(DTRX, LOGL_NOTICE, "No response from transceiver for %s\n",
+		phy_instance_name(l1h->phy_inst));
 
 	trx_ctrl_send(l1h);
 }
@@ -232,7 +229,8 @@  static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd,
 
 int trx_if_cmd_poweroff(struct trx_l1h *l1h)
 {
-	if (l1h->trx->nr == 0)
+	struct phy_instance *pinst = l1h->phy_inst;
+	if (pinst->num == 0)
 		return trx_ctrl_cmd(l1h, 1, "POWEROFF", "");
 	else
 		return 0;
@@ -240,7 +238,8 @@  int trx_if_cmd_poweroff(struct trx_l1h *l1h)
 
 int trx_if_cmd_poweron(struct trx_l1h *l1h)
 {
-	if (l1h->trx->nr == 0)
+	struct phy_instance *pinst = l1h->phy_inst;
+	if (pinst->num == 0)
 		return trx_ctrl_cmd(l1h, 1, "POWERON", "");
 	else
 		return 0;
@@ -324,6 +323,7 @@  int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
 static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
 {
 	struct trx_l1h *l1h = ofd->data;
+	struct phy_instance *pinst = l1h->phy_inst;
 	char buf[1500];
 	int len, resp;
 
@@ -374,11 +374,12 @@  static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
 		sscanf(p + 1, "%d", &resp);
 		if (resp) {
 			LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE,
-				"transceiver (trx=%d) rejected TRX command "
-				"with response: '%s'\n", l1h->trx->nr, buf);
+				"transceiver (%s) rejected TRX command "
+				"with response: '%s'\n",
+				phy_instance_name(pinst), buf);
 rsp_error:
 			if (tcm->critical) {
-				bts_shutdown(l1h->trx->bts, "SIGINT");
+				bts_shutdown(pinst->trx->bts, "SIGINT");
 				/* keep tcm list, so process is stopped */
 				return -EIO;
 			}
@@ -493,37 +494,91 @@  int trx_if_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr,
  * open/close
  */
 
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+	struct phy_instance *pinst;
+	int rc;
+
+	phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+	/* open the shared/common clock socket */
+	rc = trx_udp_open(plink, &plink->u.osmotrx.trx_ofd_clk,
+			  plink->u.osmotrx.transceiver_ip,
+			  plink->u.osmotrx.base_port_local,
+			  plink->u.osmotrx.base_port_remote,
+			  trx_clk_read_cb);
+	if (rc < 0) {
+		phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+		return -1;
+	}
+
+	/* open the individual instances with their ctrl+data sockets */
+	llist_for_each_entry(pinst, &plink->instances, list) {
+		pinst->u.osmotrx.hdl = l1if_open(pinst);
+		if (!pinst->u.osmotrx.hdl)
+			goto cleanup;
+	}
+
+	return 0;
+
+cleanup:
+	phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+	llist_for_each_entry(pinst, &plink->instances, list) {
+		if (pinst->u.osmotrx.hdl) {
+			trx_if_close(pinst->u.osmotrx.hdl);
+			pinst->u.osmotrx.hdl = NULL;
+		}
+	}
+	trx_udp_close(&plink->u.osmotrx.trx_ofd_clk);
+	return -1;
+}
+
+static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data)
+{
+	struct phy_link *plink = pinst->phy_link;
+	uint16_t inc = 1;
+
+	if (is_data)
+		inc = 2;
+
+	if (remote)
+		return plink->u.osmotrx.base_port_remote + (pinst->num << 1) + inc;
+	else
+		return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc;
+}
+
 int trx_if_open(struct trx_l1h *l1h)
 {
+	struct phy_instance *pinst = l1h->phy_inst;
+	struct phy_link *plink = pinst->phy_link;
 	int rc;
 
-	LOGP(DTRX, LOGL_NOTICE, "Open transceiver for trx=%u\n", l1h->trx->nr);
+	LOGP(DTRX, LOGL_NOTICE, "Open transceiver for %s\n",
+		phy_instance_name(pinst));
 
 	/* initialize ctrl queue */
 	INIT_LLIST_HEAD(&l1h->trx_ctrl_list);
 
 	/* open sockets */
-	if (l1h->trx->nr == 0) {
-		rc = trx_udp_open(l1h, &trx_ofd_clk, base_port_local,
-			trx_clk_read_cb);
-		if (rc < 0)
-			return rc;
-		LOGP(DTRX, LOGL_NOTICE, "Waiting for transceiver send clock\n");
-	}
 	rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl,
-		base_port_local + (l1h->trx->nr << 1) + 1, trx_ctrl_read_cb);
+			  plink->u.osmotrx.transceiver_ip,
+			  compute_port(pinst, 0, 0),
+			  compute_port(pinst, 1, 0), trx_ctrl_read_cb);
 	if (rc < 0)
 		goto err;
 	rc = trx_udp_open(l1h, &l1h->trx_ofd_data,
-		base_port_local + (l1h->trx->nr << 1) + 2, trx_data_read_cb);
+			  plink->u.osmotrx.transceiver_ip,
+			  compute_port(pinst, 0, 1),
+			  compute_port(pinst, 1, 1), trx_data_read_cb);
 	if (rc < 0)
 		goto err;
 
 	/* enable all slots */
 	l1h->config.slotmask = 0xff;
 
-	if (l1h->trx->nr == 0)
-		trx_if_cmd_poweroff(l1h);
+	/* FIXME: why was this only for TRX0 ? */
+	//if (l1h->trx->nr == 0)
+	trx_if_cmd_poweroff(l1h);
 
 	return 0;
 
@@ -548,13 +603,13 @@  void trx_if_flush(struct trx_l1h *l1h)
 
 void trx_if_close(struct trx_l1h *l1h)
 {
-	LOGP(DTRX, LOGL_NOTICE, "Close transceiver for trx=%u\n", l1h->trx->nr);
+	struct phy_instance *pinst = l1h->phy_inst;
+	LOGP(DTRX, LOGL_NOTICE, "Close transceiver for %s\n",
+		phy_instance_name(pinst));
 
 	trx_if_flush(l1h);
 
 	/* close sockets */
-	if (l1h->trx->nr == 0)
-		trx_udp_close(&trx_ofd_clk);
 	trx_udp_close(&l1h->trx_ofd_ctrl);
 	trx_udp_close(&l1h->trx_ofd_data);
 }
diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h
index 3862e2b..1ea0da9 100644
--- a/src/osmo-bts-trx/trx_if.h
+++ b/src/osmo-bts-trx/trx_if.h
@@ -6,6 +6,7 @@  extern const char *transceiver_ip;
 extern int settsc_enabled;
 extern int setbsic_enabled;
 
+struct trx_l1h;
 
 struct trx_ctrl_msg {
 	struct llist_head	list;
diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c
index a4a7909..d2feea4 100644
--- a/src/osmo-bts-trx/trx_vty.c
+++ b/src/osmo-bts-trx/trx_vty.c
@@ -45,6 +45,8 @@ 
 #include "trx_if.h"
 #include "loops.h"
 
+#define OSMOTRX_STR	"OsmoTRX Transceiver configuration\n"
+
 static struct gsm_bts *vty_bts;
 
 DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
@@ -53,7 +55,6 @@  DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
 	struct gsm_bts *bts = vty_bts;
 	struct gsm_bts_trx *trx;
 	struct trx_l1h *l1h;
-	uint8_t tn;
 
 	if (!transceiver_available) {
 		vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE);
@@ -63,7 +64,8 @@  DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
 	}
 
 	llist_for_each_entry(trx, &bts->trx_list, list) {
-		l1h = trx_l1h_hdl(trx);
+		struct phy_instance *pinst = trx_phy_instance(trx);
+		l1h = pinst->u.osmotrx.hdl;
 		vty_out(vty, "TRX %d%s", trx->nr, VTY_NEWLINE);
 		vty_out(vty, " %s%s",
 			(l1h->config.poweron) ? "poweron":"poweroff",
@@ -85,56 +87,70 @@  DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
 				VTY_NEWLINE);
 		else
 			vty_out(vty, " bisc   : undefined%s", VTY_NEWLINE);
-		if (l1h->config.rxgain_valid)
-			vty_out(vty, " rxgain : %d%s", l1h->config.rxgain,
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst)
+{
+	uint8_t tn;
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+	vty_out(vty, "PHY Instance %s%s",
+		phy_instance_name(pinst), VTY_NEWLINE);
+	if (l1h->config.maxdly_valid)
+		vty_out(vty, " maxdly : %d%s", l1h->config.maxdly,
+			VTY_NEWLINE);
+	else
+		vty_out(vty, " maxdly : undefined%s", VTY_NEWLINE);
+	for (tn = 0; tn < TRX_NR_TS; tn++) {
+		if (!((1 << tn) & l1h->config.slotmask))
+			vty_out(vty, " slot #%d: unsupported%s", tn,
 				VTY_NEWLINE);
-		else
-			vty_out(vty, " rxgain : undefined%s", VTY_NEWLINE);
-		if (l1h->config.power_valid)
-			vty_out(vty, " power  : %d%s", l1h->config.power,
+		else if (l1h->config.slottype_valid[tn])
+			vty_out(vty, " slot #%d: type %d%s", tn,
+				l1h->config.slottype[tn],
 				VTY_NEWLINE);
 		else
-			vty_out(vty, " power  : undefined%s", VTY_NEWLINE);
-		if (l1h->config.maxdly_valid)
-			vty_out(vty, " maxdly : %d%s", l1h->config.maxdly,
+			vty_out(vty, " slot #%d: undefined%s", tn,
 				VTY_NEWLINE);
-		else
-			vty_out(vty, " maxdly : undefined%s", VTY_NEWLINE);
-		for (tn = 0; tn < TRX_NR_TS; tn++) {
-			if (!((1 << tn) & l1h->config.slotmask))
-				vty_out(vty, " slot #%d: unsupported%s", tn,
-					VTY_NEWLINE);
-			else if (l1h->config.slottype_valid[tn])
-				vty_out(vty, " slot #%d: type %d%s", tn,
-					l1h->config.slottype[tn],
-					VTY_NEWLINE);
-			else
-				vty_out(vty, " slot #%d: undefined%s", tn,
-					VTY_NEWLINE);
-		}
 	}
-
-	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_bts_fn_advance, cfg_bts_fn_advance_cmd,
-	"fn-advance <0-30>",
-	"Set the number of frames to be transmitted to transceiver in advance "
-	"of current FN\n"
-	"Advance in frames\n")
+static void show_phy_single(struct vty *vty, struct phy_link *plink)
 {
-	trx_clock_advance = atoi(argv[0]);
+	struct phy_instance *pinst;
 
-	return CMD_SUCCESS;
+	vty_out(vty, "PHY %u%s", plink->num, VTY_NEWLINE);
+
+	if (plink->u.osmotrx.rxgain_valid)
+		vty_out(vty, " rx-gain        : %d dB%s",
+			plink->u.osmotrx.rxgain, VTY_NEWLINE);
+	else
+		vty_out(vty, " rx-gain        : undefined%s", VTY_NEWLINE);
+	if (plink->u.osmotrx.power_valid)
+		vty_out(vty, " tx-attenuation : %d dB%s",
+			plink->u.osmotrx.power, VTY_NEWLINE);
+	else
+		vty_out(vty, " tx-attenuation : undefined%s", VTY_NEWLINE);
+
+	llist_for_each_entry(pinst, &plink->instances, list)
+		show_phy_inst_single(vty, pinst);
 }
 
-DEFUN(cfg_bts_rts_advance, cfg_bts_rts_advance_cmd,
-	"rts-advance <0-30>",
-	"Set the number of frames to be requested (PCU) in advance of current "
-	"FN. Do not change this, unless you have a good reason!\n"
-	"Advance in frames\n")
+DEFUN(show_phy, show_phy_cmd, "show phy",
+	SHOW_STR  "Display information about the available PHYs")
 {
-	trx_rts_advance = atoi(argv[0]);
+	int i;
+
+	for (i = 0; i < 255; i++) {
+		struct phy_link *plink = phy_link_by_num(i);
+		if (!plink)
+			break;
+		show_phy_single(vty, plink);
+	}
 
 	return CMD_SUCCESS;
 }
@@ -220,63 +236,14 @@  DEFUN(cfg_bts_no_setbsic, cfg_bts_no_setbsic_cmd,
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_rxgain, cfg_trx_rxgain_cmd,
-	"rxgain <0-50>",
-	"Set the receiver gain in dB\n"
-	"Gain in dB\n")
-{
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
 
-	l1h->config.rxgain = atoi(argv[0]);
-	l1h->config.rxgain_valid = 1;
-	l1h->config.rxgain_sent = 0;
-	l1if_provision_transceiver_trx(l1h);
-
-	return CMD_SUCCESS;
-}
-
-DEFUN(cfg_trx_power, cfg_trx_power_cmd,
-	"power <0-50>",
-	"Set the transmitter power dampening\n"
-	"Power dampening in dB\n")
-{
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
-
-	l1h->config.power = atoi(argv[0]);
-	l1h->config.power_oml = 0;
-	l1h->config.power_valid = 1;
-	l1h->config.power_sent = 0;
-	l1if_provision_transceiver_trx(l1h);
-
-	return CMD_SUCCESS;
-}
-
-DEFUN(cfg_trx_poweroml_, cfg_trx_power_oml_cmd,
-	"power oml",
-	"Set the transmitter power dampening\n"
-	"Given by NM_ATT_RF_MAXPOWR_R (max power reduction) via OML\n")
-{
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
-
-	l1h->config.power = trx->max_power_red;
-	l1h->config.power_oml = 1;
-	l1h->config.power_valid = 1;
-	l1h->config.power_sent = 0;
-	l1if_provision_transceiver_trx(l1h);
-
-	return CMD_SUCCESS;
-}
-
-DEFUN(cfg_trx_maxdly, cfg_trx_maxdly_cmd,
-	"maxdly <0-31>",
+DEFUN(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd,
+	"osmotrx maxdly <0-31>",
 	"Set the maximum delay of GSM symbols\n"
 	"GSM symbols (approx. 1.1km per symbol)\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = vty->index;
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 
 	l1h->config.maxdly = atoi(argv[0]);
 	l1h->config.maxdly_valid = 1;
@@ -286,7 +253,7 @@  DEFUN(cfg_trx_maxdly, cfg_trx_maxdly_cmd,
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_slotmask, cfg_trx_slotmask_cmd,
+DEFUN(cfg_phyinst_slotmask, cfg_phyinst_slotmask_cmd,
 	"slotmask (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0)",
 	"Set the supported slots\n"
 	"TS0 supported\nTS0 unsupported\nTS1 supported\nTS1 unsupported\n"
@@ -294,8 +261,8 @@  DEFUN(cfg_trx_slotmask, cfg_trx_slotmask_cmd,
 	"TS4 supported\nTS4 unsupported\nTS5 supported\nTS5 unsupported\n"
 	"TS6 supported\nTS6 unsupported\nTS7 supported\nTS7 unsupported\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_instance *pinst = vty->index;
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 	uint8_t tn;
 
 	l1h->config.slotmask = 0;
@@ -306,76 +273,171 @@  DEFUN(cfg_trx_slotmask, cfg_trx_slotmask_cmd,
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_no_rxgain, cfg_trx_no_rxgain_cmd,
-	"no rxgain <0-50>",
-	NO_STR "Unset the receiver gain in dB\n"
+
+DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd,
+	"osmotrx fn-advance <0-30>",
+	OSMOTRX_STR
+	"Set the number of frames to be transmitted to transceiver in advance "
+	"of current FN\n"
+	"Advance in frames\n")
+{
+	struct phy_link *plink = vty->index;
+
+	plink->u.osmotrx.clock_advance = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd,
+	"osmotrx rts-advance <0-30>",
+	OSMOTRX_STR
+	"Set the number of frames to be requested (PCU) in advance of current "
+	"FN. Do not change this, unless you have a good reason!\n"
+	"Advance in frames\n")
+{
+	struct phy_link *plink = vty->index;
+
+	plink->u.osmotrx.rts_advance = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_rxgain, cfg_phy_rxgain_cmd,
+	"osmotrx rx-gain <0-50>",
+	OSMOTRX_STR
+	"Set the receiver gain in dB\n"
 	"Gain in dB\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_link *plink = vty->index;
 
-	l1h->config.rxgain_valid = 0;
+	plink->u.osmotrx.rxgain = atoi(argv[0]);
+	plink->u.osmotrx.rxgain_valid = 1;
+	plink->u.osmotrx.rxgain_sent = 0;
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_no_power, cfg_trx_no_power_cmd,
-	"no power <0-50>",
-	NO_STR "Unset the transmitter power dampening\n"
-	"Power dampening in dB\n")
+DEFUN(cfg_phy_tx_atten, cfg_phy_tx_atten_cmd,
+	"osmotrx tx-attenuation <0-50>",
+	OSMOTRX_STR
+	"Set the transmitter attenuation\n"
+	"Fixed attenuation in dB, overriding OML\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_link *plink = vty->index;
 
-	l1h->config.power_valid = 0;
+	plink->u.osmotrx.power = atoi(argv[0]);
+	plink->u.osmotrx.power_oml = 0;
+	plink->u.osmotrx.power_valid = 1;
+	plink->u.osmotrx.power_sent = 0;
 
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_trx_no_maxdly, cfg_trx_no_maxdly_cmd,
-	"no maxdly <0-31>",
-	NO_STR "Unset the maximum delay of GSM symbols\n"
-	"GSM symbols (approx. 1.1km per symbol)\n")
+DEFUN(cfg_phy_tx_atten_oml, cfg_phy_tx_atten_oml_cmd,
+	"osmotrx tx-attenuation oml",
+	OSMOTRX_STR
+	"Set the transmitter attenuation\n"
+	"Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML\n")
+{
+	struct phy_link *plink = vty->index;
+
+	plink->u.osmotrx.power_oml = 1;
+	plink->u.osmotrx.power_valid = 1;
+	plink->u.osmotrx.power_sent = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_rxgain, cfg_phy_no_rxgain_cmd,
+	"no osmotrx rx-gain",
+	NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n")
+{
+	struct phy_link *plink = vty->index;
+
+	plink->u.osmotrx.rxgain_valid = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_tx_atten, cfg_phy_no_tx_atten_cmd,
+	"no osmotrx tx-attenuation",
+	NO_STR OSMOTRX_STR "Unset the transmitter attenuation\n")
 {
-	struct gsm_bts_trx *trx = vty->index;
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_link *plink = vty->index;
+
+	plink->u.osmotrx.power_valid = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd,
+	"no osmotrx maxdly",
+	NO_STR "Unset the maximum delay of GSM symbols\n")
+{
+	struct phy_instance *pinst = vty->index;
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
 
 	l1h->config.maxdly_valid = 0;
 
 	return CMD_SUCCESS;
 }
 
-void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+DEFUN(cfg_phy_transc_ip, cfg_phy_transc_ip_cmd,
+	"osmotrx ip HOST",
+	OSMOTRX_STR
+	"Set remote IP address\n"
+	"IP address of OsmoTRX\n")
 {
-	vty_out(vty, " fn-advance %d%s", trx_clock_advance, VTY_NEWLINE);
-	vty_out(vty, " rts-advance %d%s", trx_rts_advance, VTY_NEWLINE);
+	struct phy_link *plink = vty->index;
 
-	if (trx_ms_power_loop)
-		vty_out(vty, " ms-power-loop %d%s", trx_target_rssi,
-			VTY_NEWLINE);
-	else
-		vty_out(vty, " no ms-power-loop%s", VTY_NEWLINE);
-	vty_out(vty, " %stiming-advance-loop%s", (trx_ta_loop) ? "":"no ",
-		VTY_NEWLINE);
-	if (settsc_enabled)
-		vty_out(vty, " settsc%s", VTY_NEWLINE);
-	if (setbsic_enabled)
-		vty_out(vty, " setbsic%s", VTY_NEWLINE);
+	if (plink->u.osmotrx.transceiver_ip)
+		talloc_free(plink->u.osmotrx.transceiver_ip);
+	plink->u.osmotrx.transceiver_ip = talloc_strdup(plink, argv[0]);
+
+	return CMD_SUCCESS;
 }
 
-void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd,
+	"osmotrx base-port (local|remote) <0-65535>",
+	OSMOTRX_STR "Set base UDP port number\n" "Local UDP port\n"
+	"Remote UDP port\n" "UDP base port number\n")
 {
-	struct trx_l1h *l1h = trx_l1h_hdl(trx);
+	struct phy_link *plink = vty->index;
+
+	if (!strcmp(argv[0], "local"))
+		plink->u.osmotrx.base_port_local = atoi(argv[1]);
+	else
+		plink->u.osmotrx.base_port_remote = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
 
-	if (l1h->config.rxgain_valid)
-		vty_out(vty, "  rxgain %d%s", l1h->config.rxgain, VTY_NEWLINE);
-	if (l1h->config.power_valid) {
-		if (l1h->config.power_oml)
-			vty_out(vty, "  power oml%s", VTY_NEWLINE);
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+	if (plink->u.osmotrx.transceiver_ip)
+		vty_out(vty, " osmotrx ip %s%s",
+			plink->u.osmotrx.transceiver_ip, VTY_NEWLINE);
+
+	vty_out(vty, " osmotrx fn-advance %d%s",
+		plink->u.osmotrx.clock_advance, VTY_NEWLINE);
+	vty_out(vty, " osmotrx rts-advance %d%s",
+		plink->u.osmotrx.rts_advance, VTY_NEWLINE);
+	if (plink->u.osmotrx.rxgain_valid)
+		vty_out(vty, " osmotrx rx-gain %d%s",
+			plink->u.osmotrx.rxgain, VTY_NEWLINE);
+	if (plink->u.osmotrx.power_valid) {
+		if (plink->u.osmotrx.power_oml)
+			vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE);
 		else
-			vty_out(vty, "  power %d%s", l1h->config.power,
-				VTY_NEWLINE);
+			vty_out(vty, " osmotrx tx-attenuation %d%s",
+				plink->u.osmotrx.power, VTY_NEWLINE);
 	}
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+	struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
 	if (l1h->config.maxdly_valid)
 		vty_out(vty, "  maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE);
 	if (l1h->config.slotmask != 0xff)
@@ -391,14 +453,32 @@  void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
 			VTY_NEWLINE);
 }
 
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	if (trx_ms_power_loop)
+		vty_out(vty, " ms-power-loop %d%s", trx_target_rssi,
+			VTY_NEWLINE);
+	else
+		vty_out(vty, " no ms-power-loop%s", VTY_NEWLINE);
+	vty_out(vty, " %stiming-advance-loop%s", (trx_ta_loop) ? "":"no ",
+		VTY_NEWLINE);
+	if (settsc_enabled)
+		vty_out(vty, " settsc%s", VTY_NEWLINE);
+	if (setbsic_enabled)
+		vty_out(vty, " setbsic%s", VTY_NEWLINE);
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+}
+
 int bts_model_vty_init(struct gsm_bts *bts)
 {
 	vty_bts = bts;
 
 	install_element_ve(&show_transceiver_cmd);
+	install_element_ve(&show_phy_cmd);
 
-	install_element(BTS_NODE, &cfg_bts_fn_advance_cmd);
-	install_element(BTS_NODE, &cfg_bts_rts_advance_cmd);
 	install_element(BTS_NODE, &cfg_bts_ms_power_loop_cmd);
 	install_element(BTS_NODE, &cfg_bts_no_ms_power_loop_cmd);
 	install_element(BTS_NODE, &cfg_bts_timing_advance_loop_cmd);
@@ -408,14 +488,19 @@  int bts_model_vty_init(struct gsm_bts *bts)
 	install_element(BTS_NODE, &cfg_bts_no_settsc_cmd);
 	install_element(BTS_NODE, &cfg_bts_no_setbsic_cmd);
 
-	install_element(TRX_NODE, &cfg_trx_rxgain_cmd);
-	install_element(TRX_NODE, &cfg_trx_power_cmd);
-	install_element(TRX_NODE, &cfg_trx_power_oml_cmd);
-	install_element(TRX_NODE, &cfg_trx_maxdly_cmd);
-	install_element(TRX_NODE, &cfg_trx_slotmask_cmd);
-	install_element(TRX_NODE, &cfg_trx_no_rxgain_cmd);
-	install_element(TRX_NODE, &cfg_trx_no_power_cmd);
-	install_element(TRX_NODE, &cfg_trx_no_maxdly_cmd);
+	install_element(PHY_NODE, &cfg_phy_base_port_cmd);
+	install_element(PHY_NODE, &cfg_phy_fn_advance_cmd);
+	install_element(PHY_NODE, &cfg_phy_rts_advance_cmd);
+	install_element(PHY_NODE, &cfg_phy_transc_ip_cmd);
+	install_element(PHY_NODE, &cfg_phy_rxgain_cmd);
+	install_element(PHY_NODE, &cfg_phy_tx_atten_cmd);
+	install_element(PHY_NODE, &cfg_phy_tx_atten_oml_cmd);
+	install_element(PHY_NODE, &cfg_phy_no_rxgain_cmd);
+	install_element(PHY_NODE, &cfg_phy_no_tx_atten_cmd);
+
+	install_element(PHY_INST_NODE, &cfg_phyinst_slotmask_cmd);
+	install_element(PHY_INST_NODE, &cfg_phyinst_maxdly_cmd);
+	install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdly_cmd);
 
 	return 0;
 }