VOL-1023 - Supporting multiple UNI per ONU

Added support for multiple UNIs per ONU by enabling an opt-in parameter in Resource Manager Profile
attribute key "uni_id_start" and "uni_id_end". This represents the 0 based local-device uni
index range. So, for an ONU to support a single UNI, the default (including omitted) value is 0.
To support multiple UNIs per ONU, set "uni_id_end" to the maximum (0-based) index on the ONU.

Plumbed in support throughout for multiple UNIs. Each UNI receives a dedicated TCONT/GEM from
TechProfile and is applied a dedicated MAC Bridge Instance in the ONU. Each UNI is effectively
treated the same whether on the same or different ONUs.

uni_id is used throughout to be the ONU-relative device port number (0-based)
port_no is the logical port number

Change-Id: I443d2322a2d414a358f1e0c629779c4929ce13c8
diff --git a/common/pon_resource_manager/resource_manager.py b/common/pon_resource_manager/resource_manager.py
index 9e249fb..a88b407 100644
--- a/common/pon_resource_manager/resource_manager.py
+++ b/common/pon_resource_manager/resource_manager.py
@@ -43,6 +43,7 @@
     """Implements APIs to initialize/allocate/release alloc/gemport/onu IDs."""
 
     # Constants to identify resource pool
+    UNI_ID = 'UNI_ID'
     ONU_ID = 'ONU_ID'
     ALLOC_ID = 'ALLOC_ID'
     GEMPORT_ID = 'GEMPORT_ID'
@@ -67,12 +68,16 @@
         "gemport_id_end": 8960,
         "flow_id_start": 1,
         "flow_id_end": 16383,
+        "uni_id_start": 0,
+        "uni_id_end": 0,
         "pon_ports": 16
     }
 
     '''
     # constants used as keys to reference the resource range parameters from
     # and external KV store.
+    UNI_ID_START_IDX = "uni_id_start"
+    UNI_ID_END_IDX = "uni_id_end"
     ONU_ID_START_IDX = "onu_id_start"
     ONU_ID_END_IDX = "onu_id_end"
     ONU_ID_SHARED_IDX = "onu_id_shared"
@@ -202,7 +207,16 @@
             resource_range_config = result
 
             if resource_range_config is not None:
-                self.pon_resource_ranges = json.loads(resource_range_config)
+                # update internal ranges from kv ranges. If there are missing
+                # values in the KV profile, continue to use the defaults
+                for key,value in json.loads(resource_range_config): self.pon_resource_ranges[key] = value
+
+                # initialize optional elements that may not be in the profile
+                if self.pon_resource_ranges[PONResourceManager.UNI_ID_START_IDX] is None:
+                    self.pon_resource_ranges[PONResourceManager.UNI_ID_START_IDX] = 0
+                if self.pon_resource_ranges[PONResourceManager.UNI_ID_END_IDX] is None:
+                    self.pon_resource_ranges[PONResourceManager.UNI_ID_END_IDX] = 0
+
                 self._log.debug("Init-resource-ranges-from-kvstore-success",
                                 pon_resource_ranges=self.pon_resource_ranges,
                                 path=path)
@@ -213,7 +227,8 @@
                                 e=e)
         return False
 
-    def update_range_(self, start_idx, start, end_idx, end, shared_idx, shared_pool_id, shared_resource_mgr):
+    def update_range_(self, start_idx, start, end_idx, end, shared_idx = None, shared_pool_id = None,
+                      shared_resource_mgr = None):
         if (start is not None) and \
                 (start_idx not in self.pon_resource_ranges or self.pon_resource_ranges[start_idx] < start):
             self.pon_resource_ranges[start_idx] = start
@@ -243,7 +258,9 @@
                       flow_id_start_idx=None,
                       flow_id_end_idx=None,
                       flow_id_shared_pool_id=None,
-                      flow_id_shared_resource_mgr=None):
+                      flow_id_shared_resource_mgr=None,
+                      uni_id_start_idx=None,
+                      uni_id_end_idx=None):
 
         self.update_range_(PONResourceManager.ONU_ID_START_IDX, onu_id_start_idx,
                            PONResourceManager.ONU_ID_END_IDX, onu_id_end_idx,
@@ -265,6 +282,9 @@
                            PONResourceManager.FLOW_ID_SHARED_IDX, flow_id_shared_pool_id,
                            flow_id_shared_resource_mgr)
 
+        self.update_range_(PONResourceManager.UNI_ID_START_IDX, uni_id_start_idx,
+                           PONResourceManager.UNI_ID_END_IDX, uni_id_end_idx)
+
     def init_default_pon_resource_ranges(self,
                                          onu_id_start_idx=1,
                                          onu_id_end_idx=127,
@@ -278,6 +298,8 @@
                                          flow_id_start_idx=1,
                                          flow_id_end_idx=16383,
                                          flow_id_shared_pool_id=None,
+                                         uni_id_start_idx=0,
+                                         uni_id_end_idx=0,
                                          num_of_pon_ports=16,
                                          intf_ids=None):
         """
@@ -303,7 +325,8 @@
         self.update_ranges(onu_id_start_idx, onu_id_end_idx, onu_id_shared_pool_id, None,
                            alloc_id_start_idx, alloc_id_end_idx, alloc_id_shared_pool_id, None,
                            gemport_id_start_idx, gemport_id_end_idx, gemport_id_shared_pool_id, None,
-                           flow_id_start_idx, flow_id_end_idx, flow_id_shared_pool_id, None)
+                           flow_id_start_idx, flow_id_end_idx, flow_id_shared_pool_id, None,
+                           uni_id_start_idx, uni_id_end_idx)
 
         if intf_ids is None:
             intf_ids = range(0, num_of_pon_ports)
@@ -468,6 +491,27 @@
 
         return status
 
+    def assert_resource_limits(self, id, resource_type):
+        """
+        Assert the specified id value is in the limit bounds of he requested resource type.
+
+        :param id: The value to assert is in limits
+        :param resource_type: String to identify type of resource
+        """
+        start_idx = PONResourceManager.ONU_ID_START_IDX if resource_type == PONResourceManager.ONU_ID \
+            else PONResourceManager.ALLOC_ID_START_IDX if resource_type == PONResourceManager.ALLOC_ID \
+            else PONResourceManager.GEMPORT_ID_START_IDX if resource_type == PONResourceManager.GEMPORT_ID \
+            else PONResourceManager.FLOW_ID_START_IDX if resource_type == PONResourceManager.FLOW_ID \
+            else PONResourceManager.UNI_ID_START_IDX if resource_type == PONResourceManager.UNI_ID \
+            else None
+        end_idx = PONResourceManager.ONU_ID_END_IDX if resource_type == PONResourceManager.ONU_ID \
+            else PONResourceManager.ALLOC_ID_END_IDX if resource_type == PONResourceManager.ALLOC_ID \
+            else PONResourceManager.GEMPORT_ID_END_IDX if resource_type == PONResourceManager.GEMPORT_ID \
+            else PONResourceManager.FLOW_ID_END_IDX if resource_type == PONResourceManager.FLOW_ID \
+            else PONResourceManager.UNI_ID_END_IDX if resource_type == PONResourceManager.UNI_ID \
+            else None
+        assert id >= self.pon_resource_ranges[start_idx] and id <= self.pon_resource_ranges[end_idx]
+
     def get_resource_id(self, pon_intf_id, resource_type, num_of_id=1):
         """
         Create alloc/gemport/onu/flow id for given OLT PON interface.
diff --git a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
index f3fdf9f..fde2591 100644
--- a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -21,6 +21,9 @@
 import json
 import ast
 import structlog
+
+from collections import OrderedDict
+
 from twisted.internet import reactor, task
 from twisted.internet.defer import DeferredQueue, inlineCallbacks, returnValue, TimeoutError
 
@@ -60,7 +63,6 @@
 log = structlog.get_logger()
 
 _STARTUP_RETRY_WAIT = 20
-_MAXIMUM_PORT = 128  # UNI ports
 
 
 class BrcmOpenomciOnuHandler(object):
@@ -86,7 +88,6 @@
 
         self._onu_indication = None
         self._unis = dict()  # Port # -> UniPort
-        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)
 
         self._pon = None
         # TODO: probably shouldnt be hardcoded, determine from olt maybe?
@@ -158,19 +159,13 @@
                          if uni.name == port_no_or_name), None)
 
         assert isinstance(port_no_or_name, int), 'Invalid parameter type'
-        return self._unis.get(port_no_or_name)
+        return next((uni for uni in self.uni_ports
+                    if uni.logical_port_number == port_no_or_name), None)
 
     @property
     def pon_port(self):
         return self._pon
 
-    @property
-    def _next_port_number(self):
-        return self._port_number_pool.get_next()
-
-    def _release_port_number(self, number):
-        self._port_number_pool.release(number)
-
     def receive_message(self, msg):
         if self.omci_cc is not None:
             self.omci_cc.receive_message(msg)
@@ -275,7 +270,8 @@
         try:
             if event_msg['event'] == 'download_tech_profile':
                 tp_path = event_msg['event_data']
-                self.load_and_configure_tech_profile(tp_path)
+                uni_id = event_msg['uni_id']
+                self.load_and_configure_tech_profile(uni_id, tp_path)
 
         except Exception as e:
             self.log.error("exception-handling-onu-event", e=e)
@@ -328,7 +324,7 @@
         else:
             self.log.debug("parent-adapter-not-available")
 
-    def _create_tconts(self, us_scheduler):
+    def _create_tconts(self, uni_id, us_scheduler):
         alloc_id = us_scheduler['alloc_id']
         q_sched_policy = us_scheduler['q_sched_policy']
         self.log.debug('create-tcont', us_scheduler=us_scheduler)
@@ -336,6 +332,7 @@
         tcontdict = dict()
         tcontdict['alloc-id'] = alloc_id
         tcontdict['q_sched_policy'] = q_sched_policy
+        tcontdict['uni_id'] = uni_id
 
         # TODO: Not sure what to do with any of this...
         tddata = dict()
@@ -353,7 +350,7 @@
         self.log.debug('pon-add-tcont', tcont=tcont)
 
     # Called when there is an olt up indication, providing the gem port id chosen by the olt handler
-    def _create_gemports(self, gem_ports, alloc_id_ref, direction):
+    def _create_gemports(self, uni_id, gem_ports, alloc_id_ref, direction):
         self.log.debug('create-gemport',
                        gem_ports=gem_ports, direction=direction)
 
@@ -376,6 +373,7 @@
             gemdict['priority_q'] = gem_port['priority_q']
             gemdict['scheduling_policy'] = gem_port['scheduling_policy']
             gemdict['weight'] = gem_port['weight']
+            gemdict['uni_id'] = uni_id
 
             gem_port = OnuGemPort.create(self, gem_port=gemdict, entity_id=self._pon.next_gem_entity_id)
 
@@ -383,24 +381,31 @@
 
             self.log.debug('pon-add-gemport', gem_port=gem_port)
 
-    def _do_tech_profile_configuration(self, tp):
+    def _do_tech_profile_configuration(self, uni_id, tp):
         num_of_tconts = tp['num_of_tconts']
         us_scheduler = tp['us_scheduler']
         alloc_id = us_scheduler['alloc_id']
-        self._create_tconts(us_scheduler)
+        self._create_tconts(uni_id, us_scheduler)
         upstream_gem_port_attribute_list = tp['upstream_gem_port_attribute_list']
-        self._create_gemports(upstream_gem_port_attribute_list, alloc_id, "UPSTREAM")
+        self._create_gemports(uni_id, upstream_gem_port_attribute_list, alloc_id, "UPSTREAM")
         downstream_gem_port_attribute_list = tp['downstream_gem_port_attribute_list']
-        self._create_gemports(downstream_gem_port_attribute_list, alloc_id, "DOWNSTREAM")
+        self._create_gemports(uni_id, downstream_gem_port_attribute_list, alloc_id, "DOWNSTREAM")
 
-    def load_and_configure_tech_profile(self, tp_path):
+    def load_and_configure_tech_profile(self, uni_id, tp_path):
         self.log.debug("loading-tech-profile-configuration")
-        if tp_path not in self._tech_profile_download_done:
-            self._tech_profile_download_done[tp_path] = False
 
-        if not self._tech_profile_download_done[tp_path]:
+        if uni_id not in self._tp_service_specific_task:
+            self._tp_service_specific_task[uni_id] = dict()
+
+        if uni_id not in self._tech_profile_download_done:
+            self._tech_profile_download_done[uni_id] = dict()
+
+        if tp_path not in self._tech_profile_download_done[uni_id]:
+            self._tech_profile_download_done[uni_id][tp_path] = False
+
+        if not self._tech_profile_download_done[uni_id][tp_path]:
             try:
-                if tp_path in self._tp_service_specific_task:
+                if tp_path in self._tp_service_specific_task[uni_id]:
                     self.log.info("tech-profile-config-already-in-progress",
                                    tp_path=tp_path)
                     return
@@ -408,16 +413,16 @@
                 tp = self.kv_client[tp_path]
                 tp = ast.literal_eval(tp)
                 self.log.debug("tp-instance", tp=tp)
-                self._do_tech_profile_configuration(tp)
+                self._do_tech_profile_configuration(uni_id, tp)
 
                 def success(_results):
                     self.log.info("tech-profile-config-done-successfully")
                     device = self.adapter_agent.get_device(self.device_id)
                     device.reason = 'tech-profile-config-download-success'
                     self.adapter_agent.update_device(device)
-                    if tp_path in self._tp_service_specific_task:
-                        del self._tp_service_specific_task[tp_path]
-                    self._tech_profile_download_done[tp_path] = True
+                    if tp_path in self._tp_service_specific_task[uni_id]:
+                        del self._tp_service_specific_task[uni_id][tp_path]
+                    self._tech_profile_download_done[uni_id][tp_path] = True
 
                 def failure(_reason):
                     self.log.warn('tech-profile-config-failure-retrying',
@@ -425,16 +430,16 @@
                     device = self.adapter_agent.get_device(self.device_id)
                     device.reason = 'tech-profile-config-download-failure-retrying'
                     self.adapter_agent.update_device(device)
-                    if tp_path in self._tp_service_specific_task:
-                        del self._tp_service_specific_task[tp_path]
+                    if tp_path in self._tp_service_specific_task[uni_id]:
+                        del self._tp_service_specific_task[uni_id][tp_path]
                     self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self.load_and_configure_tech_profile,
-                                                       tp_path)
+                                                       uni_id, tp_path)
 
                 self.log.info('downloading-tech-profile-configuration')
-                self._tp_service_specific_task[tp_path] = \
-                       BrcmTpServiceSpecificTask(self.omci_agent, self)
+                self._tp_service_specific_task[uni_id][tp_path] = \
+                       BrcmTpServiceSpecificTask(self.omci_agent, self, uni_id)
                 self._deferred = \
-                       self._onu_omci_device.task_runner.queue_task(self._tp_service_specific_task[tp_path])
+                       self._onu_omci_device.task_runner.queue_task(self._tp_service_specific_task[uni_id][tp_path])
                 self._deferred.addCallbacks(success, failure)
 
             except Exception as e:
@@ -489,15 +494,18 @@
                 _in_port = fd.get_in_port(flow)
                 assert _in_port is not None
 
+                _out_port = fd.get_out_port(flow)  # may be None
+
                 if is_downstream(_in_port):
-                    self.log.debug('downstream-flow')
+                    self.log.debug('downstream-flow', in_port=_in_port, out_port=_out_port)
+                    uni_port = self.uni_port(_out_port)
                 elif is_upstream(_in_port):
-                    self.log.debug('upstream-flow')
+                    self.log.debug('upstream-flow', in_port=_in_port, out_port=_out_port)
+                    uni_port = self.uni_port(_in_port)
                 else:
                     raise Exception('port should be 1 or 2 by our convention')
 
-                _out_port = fd.get_out_port(flow)  # may be None
-                self.log.debug('out-port', out_port=_out_port)
+                self.log.debug('flow-ports', in_port=_in_port, out_port=_out_port, uni_port=str(uni_port))
 
                 for field in fd.get_ofb_fields(flow):
                     if field.type == fd.ETH_TYPE:
@@ -596,13 +604,16 @@
                 elif _set_vlan_vid is None or _set_vlan_vid == 0:
                     self.log.warn('ignorning-flow-that-does-not-set-vlanid')
                 else:
-                    self._add_vlan_filter_task(device, _set_vlan_vid)
+                    self.log.warn('set-vlanid', uni_id=uni_port.port_number, set_vlan_vid=_set_vlan_vid)
+                    self._add_vlan_filter_task(device, uni_port, _set_vlan_vid)
 
             except Exception as e:
                 self.log.exception('failed-to-install-flow', e=e, flow=flow)
 
 
-    def _add_vlan_filter_task(self, device, _set_vlan_vid):
+    def _add_vlan_filter_task(self, device, uni_port, _set_vlan_vid):
+        assert uni_port is not None
+
         def success(_results):
             self.log.info('vlan-tagging-success', _results=_results)
             device.reason = 'omci-flows-pushed'
@@ -612,10 +623,10 @@
             self.log.warn('vlan-tagging-failure', _reason=_reason)
             device.reason = 'omci-flows-failed-retrying'
             self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
-                                                       self._add_vlan_filter_task, device, _set_vlan_vid)
+                                                       self._add_vlan_filter_task, device, uni_port, _set_vlan_vid)
 
         self.log.info('setting-vlan-tag')
-        self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent, self.device_id, _set_vlan_vid)
+        self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent, self.device_id, uni_port, _set_vlan_vid)
         self._deferred = self._onu_omci_device.task_runner.queue_task(self._vlan_filter_task)
         self._deferred.addCallbacks(success, failure)
 
@@ -653,8 +664,8 @@
             reactor.callLater(0, self._onu_omci_device.stop)
 
             # Let TP download happen again
-            self._tp_service_specific_task.clear()
-            self._tech_profile_download_done.clear()
+            for i in self._tp_service_specific_task: i.clear()
+            for i in self._tech_profile_download_done: i.clear()
 
             self.disable_ports(onu_device)
             onu_device.reason = "stopping-openomci"
@@ -674,8 +685,8 @@
         reactor.callLater(0, self._onu_omci_device.stop)
 
         # Let TP download happen again
-        self._tp_service_specific_task.clear()
-        self._tech_profile_download_done.clear()
+        for i in self._tp_service_specific_task: i.clear()
+        for i in self._tech_profile_download_done: i.clear()
 
         self.disable_ports(onu_device)
         onu_device.reason = "stopping-openomci"
@@ -720,8 +731,8 @@
                 reactor.callLater(0, self._onu_omci_device.stop)
 
                 # Let TP download happen again
-                self._tp_service_specific_task.clear()
-                self._tech_profile_download_done.clear()
+                for i in self._tp_service_specific_task: i.clear()
+                for i in self._tech_profile_download_done: i.clear()
 
                 self.disable_ports(device)
                 device.oper_status = OperStatus.UNKNOWN
@@ -895,18 +906,30 @@
                     uni_value = config.uni_g_entities[entity_id]
                     self.log.debug("discovered-uni", entity_id=entity_id, value=uni_value)
 
-                # TODO: can only support one UNI per ONU at this time. break out as soon as we have a good UNI
+                uni_entities = OrderedDict()
                 for entity_id in pptp_list:
                     pptp_value = config.pptp_entities[entity_id]
                     self.log.debug("discovered-pptp", entity_id=entity_id, value=pptp_value)
-                    self._add_uni_port(entity_id, uni_type=UniType.PPTP)
-                    break
+                    uni_entities[entity_id] = UniType.PPTP
 
                 for entity_id in veip_list:
                     veip_value = config.veip_entities[entity_id]
                     self.log.debug("discovered-veip", entity_id=entity_id, value=veip_value)
-                    self._add_uni_port(entity_id, uni_type=UniType.VEIP)
-                    break
+                    uni_entities[entity_id] = UniType.VEIP
+
+                uni_id = 0
+                for entity_id, uni_type in uni_entities.iteritems():
+                    try:
+                        self._add_uni_port(entity_id, uni_id, uni_type)
+                        uni_id += 1
+                    except AssertionError as e:
+                        self.log.warn("could not add UNI", entity_id=entity_id, uni_type=uni_type, e=e)
+
+                multi_uni = len(self._unis) > 1
+                for uni_port in self._unis.itervalues():
+                    uni_port.add_logical_port(uni_port.port_number, multi_uni)
+
+                self.adapter_agent.update_device(device)
 
                 self._qos_flexibility = config.qos_configuration_flexibility or 0
                 self._omcc_version = config.omcc_version or OMCCVersion.Unknown
@@ -957,7 +980,7 @@
             self.log.info('device-info-not-loaded-skipping-mib-download')
 
 
-    def _add_uni_port(self, entity_id, uni_type=UniType.PPTP):
+    def _add_uni_port(self, entity_id, uni_id, uni_type=UniType.PPTP):
         self.log.debug('function-entry')
 
         device = self.adapter_agent.get_device(self.device_id)
@@ -970,24 +993,21 @@
         # TODO: This knowledge is locked away in openolt.  and it assumes one onu equals one uni...
         parent_device = self.adapter_agent.get_device(device.parent_id)
         parent_adapter = parent_adapter_agent.adapter.devices[parent_device.id]
-        uni_no_start = parent_adapter.platform.mk_uni_port_num(
-            self._onu_indication.intf_id, self._onu_indication.onu_id)
+        uni_no = parent_adapter.platform.mk_uni_port_num(
+            self._onu_indication.intf_id, self._onu_indication.onu_id, uni_id)
 
         # TODO: Some or parts of this likely need to move to UniPort. especially the format stuff
-        working_port = self._next_port_number
-        uni_no = uni_no_start + working_port
         uni_name = "uni-{}".format(uni_no)
 
-        mac_bridge_port_num = working_port + 1
+        mac_bridge_port_num = uni_id + 1 # TODO +1 is only to test non-zero index
 
-        self.log.debug('uni-port-inputs', uni_no=uni_no, uni_name=uni_name, uni_type=uni_type,
+        self.log.debug('uni-port-inputs', uni_no=uni_no, uni_id=uni_id, uni_name=uni_name, uni_type=uni_type,
                        entity_id=entity_id, mac_bridge_port_num=mac_bridge_port_num)
 
-        uni_port = UniPort.create(self, uni_name, uni_no, uni_name, uni_type)
+        uni_port = UniPort.create(self, uni_name, uni_id, uni_no, uni_name, uni_type)
         uni_port.entity_id = entity_id
         uni_port.enabled = True
         uni_port.mac_bridge_port_num = mac_bridge_port_num
-        uni_port.add_logical_port(uni_port.port_number)
 
         self.log.debug("created-uni-port", uni=uni_port)
 
@@ -1000,14 +1020,19 @@
                                                                   uni_ports=self._unis.values())
         # TODO: this should be in the PonPortclass
         pon_port = self._pon.get_port()
-        self.adapter_agent.delete_port_reference_from_parent(self.device_id,
-                                                             pon_port)
 
-        pon_port.peers.extend([Port.PeerPort(device_id=device.parent_id,
-                                             port_no=uni_port.port_number)])
+        # Delete reference to my own UNI as peer from parent.
+        # TODO why is this here, add_port_reference_to_parent already prunes duplicates
+        me_as_peer = Port.PeerPort(device_id=device.parent_id, port_no=uni_port.port_number)
+        partial_pon_port = Port(port_no=pon_port.port_no, label=pon_port.label,
+                                type=pon_port.type, admin_state=pon_port.admin_state,
+                                oper_status=pon_port.oper_status,
+                                peers=[me_as_peer]) # only list myself as a peer to avoid deleting all other UNIs from parent
+        self.adapter_agent.delete_port_reference_from_parent(self.device_id, partial_pon_port)
+
+        pon_port.peers.extend([me_as_peer])
 
         self._pon._port = pon_port
 
         self.adapter_agent.add_port_reference_to_parent(self.device_id,
-                                                        pon_port)
-        self.adapter_agent.update_device(device)
+                                                        pon_port)
\ No newline at end of file
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
index 14041a4..6dea69e 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
@@ -81,9 +81,6 @@
         # Frame size
         self._max_gem_payload = DEFAULT_GEM_PAYLOAD
 
-        # TODO: only using a single UNI/ethernet port
-        self._uni_port = self._handler.uni_ports[0]
-
         self._pon = handler.pon_port
 
         # Port numbers
@@ -188,22 +185,32 @@
                 device.reason = 'performing-initial-mib-download'
                 self._handler.adapter_agent.update_device(device)
 
-                try:
-                    # Lock the UNI ports to prevent any alarms during initial configuration
-                    # of the ONU
-                    self.strobe_watchdog()
-                    yield self.enable_uni(self._uni_port, True)
+            try:
+                # Lock the UNI ports to prevent any alarms during initial configuration
+                # of the ONU
+                self.strobe_watchdog()
+
+                # Provision the initial bridge configuration
+                yield self.perform_initial_bridge_setup()
+
+                for uni_port in self._handler.uni_ports:
+                    yield self.enable_uni(uni_port, True)
 
                     # Provision the initial bridge configuration
-                    yield self.perform_initial_bridge_setup()
+                    yield self.perform_uni_initial_bridge_setup(uni_port)
 
                     # And re-enable the UNIs if needed
-                    yield self.enable_uni(self._uni_port, False)
+                    yield self.enable_uni(uni_port, False)
 
                     self.deferred.callback('initial-download-success')
 
-                except TimeoutError as e:
-                    self.deferred.errback(failure.Failure(e))
+            except TimeoutError as e:
+                self.log.error('initial-download-failure', e=e)
+                self.deferred.errback(failure.Failure(e))
+
+            except Exception as e:
+                self.log.exception('initial-download-failure', e=e)
+                self.deferred.errback(failure.Failure(e))
 
             else:
                 e = MibResourcesFailure('Required resources are not available',
@@ -237,6 +244,22 @@
             results = yield omci_cc.send(frame)
             self.check_status_and_state(results, 'create-gal-ethernet-profile')
 
+        except TimeoutError as e:
+            self.log.warn('rx-timeout-0', e=e)
+            raise
+
+        except Exception as e:
+            self.log.exception('omci-setup-0', e=e)
+            raise
+
+        returnValue(None)
+
+    @inlineCallbacks
+    def perform_uni_initial_bridge_setup(self, uni_port):
+        self.log.debug('function-entry')
+        omci_cc = self._onu_device.omci_cc
+        frame = None
+        try:
             ################################################################################
             # Common - PON and/or UNI                                                      #
             ################################################################################
@@ -258,7 +281,7 @@
                 'unknown_mac_address_discard': True
             }
             msg = MacBridgeServiceProfileFrame(
-                self._mac_bridge_service_profile_entity_id,
+                self._mac_bridge_service_profile_entity_id + uni_port.mac_bridge_port_num,
                 attributes
             )
             frame = msg.create()
@@ -277,7 +300,7 @@
             #            - Nothing at this point. When a GEM port is created, this entity will
             #              be updated to reference the GEM Interworking TP
 
-            msg = Ieee8021pMapperServiceProfileFrame(self._ieee_mapper_service_profile_entity_id)
+            msg = Ieee8021pMapperServiceProfileFrame(self._ieee_mapper_service_profile_entity_id + uni_port.mac_bridge_port_num)
             frame = msg.create()
             self.log.debug('openomci-msg', omci_msg=msg)
             results = yield omci_cc.send(frame)
@@ -300,11 +323,11 @@
 
             # TODO: magic. make a static variable for tp_type
             msg = MacBridgePortConfigurationDataFrame(
-                self._mac_bridge_port_ani_entity_id,
-                bridge_id_pointer=self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                port_num=self._pon_port_num,  # Port ID  ##TODO associated with what?
-                tp_type=3,  # TP Type (IEEE 802.1p mapper service)
-                tp_pointer=self._ieee_mapper_service_profile_entity_id  # TP ID, 8021p mapper ID
+                self._mac_bridge_port_ani_entity_id + uni_port.mac_bridge_port_num,
+                bridge_id_pointer=self._mac_bridge_service_profile_entity_id + uni_port.mac_bridge_port_num,  # Bridge Entity ID
+                port_num= 0xff, # Port ID - unique number within the bridge
+                tp_type=3, # TP Type (IEEE 802.1p mapper service)
+                tp_pointer=self._ieee_mapper_service_profile_entity_id + uni_port.mac_bridge_port_num  # TP ID, 8021p mapper ID
             )
             frame = msg.create()
             self.log.debug('openomci-msg', omci_msg=msg)
@@ -323,8 +346,8 @@
 
             # TODO: magic. make a static variable for forward_op
             msg = VlanTaggingFilterDataFrame(
-                self._mac_bridge_port_ani_entity_id,  # Entity ID
-                vlan_tcis=[self._vlan_tcis_1],  # VLAN IDs
+                self._mac_bridge_port_ani_entity_id + uni_port.mac_bridge_port_num,  # Entity ID
+                vlan_tcis=[self._vlan_tcis_1],        # VLAN IDs
                 forward_operation=0x10
             )
             frame = msg.create()
@@ -348,19 +371,20 @@
             # TODO: magic. make a static variable for tp_type
 
             # default to PPTP
-            if self._uni_port.type is UniType.VEIP:
+            tp_type = None
+            if uni_port.type is UniType.VEIP:
                 tp_type = 11
-            elif self._uni_port.type is UniType.PPTP:
+            elif uni_port.type is UniType.PPTP:
                 tp_type = 1
             else:
                 tp_type = 1
 
             msg = MacBridgePortConfigurationDataFrame(
-                self._uni_port.entity_id,  # Entity ID - This is read-only/set-by-create !!!
-                bridge_id_pointer=self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                port_num=self._uni_port.mac_bridge_port_num,  # Port ID
-                tp_type=tp_type,  # PPTP Ethernet or VEIP UNI
-                tp_pointer=self._uni_port.entity_id  # Ethernet UNI ID
+                uni_port.entity_id,            # Entity ID - This is read-only/set-by-create !!!
+                bridge_id_pointer=self._mac_bridge_service_profile_entity_id + uni_port.mac_bridge_port_num,  # Bridge Entity ID
+                port_num=uni_port.mac_bridge_port_num,   # Port ID
+                tp_type=tp_type,                         # PPTP Ethernet or VEIP UNI
+                tp_pointer=uni_port.entity_id            # Ethernet UNI ID
             )
             frame = msg.create()
             self.log.debug('openomci-msg', omci_msg=msg)
@@ -378,216 +402,6 @@
         returnValue(None)
 
     @inlineCallbacks
-    def perform_service_specific_steps(self):
-        self.log.debug('function-entry')
-
-        omci_cc = self._onu_device.omci_cc
-        frame = None
-
-        try:
-            ################################################################################
-            # TCONTS
-            #
-            #  EntityID will be referenced by:
-            #            - GemPortNetworkCtp
-            #  References:
-            #            - ONU created TCONT (created on ONU startup)
-
-            tcont_idents = self._onu_device.query_mib(Tcont.class_id)
-            self.log.debug('tcont-idents', tcont_idents=tcont_idents)
-
-            for tcont in self._handler.pon_port.tconts.itervalues():
-                free_entity_id = None
-                for k, v in tcont_idents.items():
-                    alloc_check = v.get('attributes', {}).get('alloc_id', 0)
-                    # Some onu report both to indicate an available tcont
-                    if alloc_check == 0xFF or alloc_check == 0xFFFF:
-                        free_entity_id = k
-                        break
-                    else:
-                        free_entity_id = None
-
-                self.log.debug('tcont-loop', free_entity_id=free_entity_id)
-
-                if free_entity_id is None:
-                    self.log.error('no-available-tconts')
-                    break
-
-                # TODO: Need to restore on failure.  Need to check status/results
-                yield tcont.add_to_hardware(omci_cc, free_entity_id)
-
-            ################################################################################
-            # GEMS  (GemPortNetworkCtp and GemInterworkingTp)
-            #
-            #  For both of these MEs, the entity_id is the GEM Port ID. The entity id of the
-            #  GemInterworkingTp ME could be different since it has an attribute to specify
-            #  the GemPortNetworkCtp entity id.
-            #
-            #        for the GemPortNetworkCtp ME
-            #
-            #  GemPortNetworkCtp
-            #    EntityID will be referenced by:
-            #              - GemInterworkingTp
-            #    References:
-            #              - TCONT
-            #              - Hardcoded upstream TM Entity ID
-            #              - (Possibly in Future) Upstream Traffic descriptor profile pointer
-            #
-            #  GemInterworkingTp
-            #    EntityID will be referenced by:
-            #              - Ieee8021pMapperServiceProfile
-            #    References:
-            #              - GemPortNetworkCtp
-            #              - Ieee8021pMapperServiceProfile
-            #              - GalEthernetProfile
-            #
-
-            for gem_port in self._handler.pon_port.gem_ports.itervalues():
-                tcont = gem_port.tcont
-                if tcont is None:
-                    self.log.error('unknown-tcont-reference', gem_id=gem_port.gem_id)
-                    continue
-
-                # TODO: Need to restore on failure.  Need to check status/results
-                yield gem_port.add_to_hardware(omci_cc,
-                                               tcont.entity_id,
-                                               self._ieee_mapper_service_profile_entity_id,
-                                               self._gal_enet_profile_entity_id)
-
-            ################################################################################
-            # Update the IEEE 802.1p Mapper Service Profile config
-            #
-            #  EntityID was created prior to this call. This is a set
-            #
-            #  References:
-            #            - Gem Interwork TPs are set here
-            #
-            # TODO: All p-bits currently go to the one and only GEMPORT ID for now
-            gem_ports = self._handler.pon_port.gem_ports
-            gem_entity_ids = [gem_port.entity_id for _, gem_port in gem_ports.items()] \
-                if len(gem_ports) else [OmciNullPointer]
-
-            msg = Ieee8021pMapperServiceProfileFrame(
-                self._ieee_mapper_service_profile_entity_id,  # 802.1p mapper Service Mapper Profile ID
-                interwork_tp_pointers=gem_entity_ids  # Interworking TP IDs
-            )
-            frame = msg.set()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'set-8021p-mapper-service-profile')
-
-            ################################################################################
-            # Create Extended VLAN Tagging Operation config (PON-side)
-            #
-            #  EntityID relates to the VLAN TCIS
-            #  References:
-            #            - VLAN TCIS from previously created VLAN Tagging filter data
-            #            - PPTP Ethernet or VEIP UNI
-            #
-
-            # TODO: do this for all uni/ports...
-            # TODO: magic.  static variable for assoc_type
-
-            # default to PPTP
-            if self._uni_port.type is UniType.VEIP:
-                association_type = 10
-            elif self._uni_port.type is UniType.PPTP:
-                association_type = 2
-            else:
-                association_type = 2
-
-            attributes = dict(
-                association_type=association_type,  # Assoc Type, PPTP/VEIP Ethernet UNI
-                associated_me_pointer=self._uni_port.entity_id,  # Assoc ME, PPTP/VEIP Entity Id
-
-                # See VOL-1311 - Need to set table during create to avoid exception
-                # trying to read back table during post-create-read-missing-attributes
-                # But, because this is a R/W attribute. Some ONU may not accept the
-                # value during create. It is repeated again in a set below.
-                input_tpid=self._input_tpid,  # input TPID
-                output_tpid=self._output_tpid,  # output TPID
-            )
-
-            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                attributes=attributes
-            )
-
-            frame = msg.create()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'create-extended-vlan-tagging-operation-configuration-data')
-
-            attributes = dict(
-                # Specifies the TPIDs in use and that operations in the downstream direction are
-                # inverse to the operations in the upstream direction
-                input_tpid=self._input_tpid,  # input TPID
-                output_tpid=self._output_tpid,  # output TPID
-                downstream_mode=0,              # inverse of upstream
-            )
-
-            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                attributes=attributes
-            )
-
-            frame = msg.set()
-            self.log.debug('openomci-msg', msg=msg)
-            results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data')
-
-            attributes = dict(
-                # parameters: Entity Id ( 0x900), Filter Inner Vlan Id(0x1000-4096,do not filter on Inner vid,
-                #             Treatment Inner Vlan Id : 2
-
-                # Update uni side extended vlan filter
-                # filter for untagged
-                # probably for eapol
-                # TODO: lots of magic
-                # TODO: magic 0x1000 / 4096?
-                received_frame_vlan_tagging_operation_table=
-                VlanTaggingOperation(
-                    filter_outer_priority=15,  # This entry is not a double-tag rule
-                    filter_outer_vid=4096,  # Do not filter on the outer VID value
-                    filter_outer_tpid_de=0,  # Do not filter on the outer TPID field
-
-                    filter_inner_priority=15,
-                    filter_inner_vid=4096,
-                    filter_inner_tpid_de=0,
-                    filter_ether_type=0,
-
-                    treatment_tags_to_remove=0,
-                    treatment_outer_priority=15,
-                    treatment_outer_vid=0,
-                    treatment_outer_tpid_de=0,
-
-                    treatment_inner_priority=0,
-                    treatment_inner_vid=self._cvid,
-                    treatment_inner_tpid_de=4,
-                )
-            )
-
-            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                attributes=attributes
-            )
-
-            frame = msg.set()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data-table')
-
-        except TimeoutError as e:
-            self.log.warn('rx-timeout-2', e=e)
-            raise
-
-        except Exception as e:
-            self.log.exception('omci-setup-2', e=e)
-            raise
-
-        returnValue(None)
-
-    @inlineCallbacks
     def enable_uni(self, uni_port, force_lock):
         """
         Lock or unlock a single uni port
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_tp_service_specific_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_tp_service_specific_task.py
index 26f7c66..89d39e3 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_tp_service_specific_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_tp_service_specific_task.py
@@ -48,7 +48,7 @@
 
     name = "Broadcom Tech-Profile Download Task"
 
-    def __init__(self, omci_agent, handler):
+    def __init__(self, omci_agent, handler, uni_id):
         """
         Class initialization
 
@@ -71,7 +71,7 @@
         self._max_gem_payload = DEFAULT_GEM_PAYLOAD
 
         # TODO: only using a single UNI/ethernet port
-        self._uni_port = self._handler.uni_ports[0]
+        self._uni_port = self._handler.uni_ports[uni_id]
         self._uni_port_num = self._uni_port.mac_bridge_port_num
         self._ethernet_uni_entity_id = self._uni_port.entity_id
 
@@ -179,6 +179,7 @@
             self.log.debug('tcont-idents', tcont_idents=tcont_idents)
 
             for tcont in self._handler.pon_port.tconts.itervalues():
+                if tcont.uni_id is not None and  tcont.uni_id != self._uni_port.uni_id: continue
                 free_entity_id = None
                 for k, v in tcont_idents.items():
                     alloc_check = v.get('attributes', {}).get('alloc_id', 0)
@@ -189,7 +190,7 @@
                     else:
                         free_entity_id = None
 
-                self.log.debug('tcont-loop', free_entity_id=free_entity_id)
+                self.log.debug('tcont-loop', free_entity_id=free_entity_id, alloc_id=tcont.alloc_id)
 
                 if free_entity_id is None:
                     self.log.error('no-available-tconts')
@@ -258,19 +259,22 @@
                         self.tcont_me_to_queue_map[tcont_me].append(k)
                     else:
                         uni_port = (related_port & 0xffff0000) >> 16
-                        if uni_port not in self.uni_port_to_queue_map:
-                            self.log.debug("prior-q-related-port-and-uni-port-me",
-                                            related_port=related_port,
-                                            uni_port_me=uni_port)
-                            self.uni_port_to_queue_map[uni_port] = list()
+                        if uni_port ==  self._uni_port.entity_id:
+                            if uni_port not in self.uni_port_to_queue_map:
+                                self.log.debug("prior-q-related-port-and-uni-port-me",
+                                                related_port=related_port,
+                                                uni_port_me=uni_port)
+                                self.uni_port_to_queue_map[uni_port] = list()
 
-                        self.uni_port_to_queue_map[uni_port].append(k)
+                            self.uni_port_to_queue_map[uni_port].append(k)
 
 
             self.log.debug("ul-prior-q", ul_prior_q=self.tcont_me_to_queue_map)
             self.log.debug("dl-prior-q", dl_prior_q=self.uni_port_to_queue_map)
 
             for gem_port in self._handler.pon_port.gem_ports.itervalues():
+                if gem_port.uni_id is not None and gem_port.uni_id != self._uni_port.uni_id: continue
+
                 # TODO: Traffic descriptor will be available after meter bands are available
                 tcont = gem_port.tcont
                 if tcont is None:
@@ -309,7 +313,8 @@
                     # TODO: Need to restore on failure.  Need to check status/results
                     results = yield gem_port.add_to_hardware(omci_cc,
                                                    tcont.entity_id,
-                                                   self._ieee_mapper_service_profile_entity_id,
+                                                   self._ieee_mapper_service_profile_entity_id +
+                                                             self._uni_port.mac_bridge_port_num,
                                                    self._gal_enet_profile_entity_id,
                                                    ul_prior_q_entity_id, dl_prior_q_entity_id)
                     self.check_status_and_state(results, 'create-gem-port')
@@ -329,6 +334,8 @@
 
             gem_entity_ids = [OmciNullPointer] * 8
             for gem_port in self._handler.pon_port.gem_ports.itervalues():
+                if gem_port.uni_id is not None and gem_port.uni_id != self._uni_port.uni_id: continue
+
                 if gem_port.direction == "upstream" or \
                         gem_port.direction == "bi-directional":
                     for i, p in enumerate(gem_port.pbit_map):
@@ -340,7 +347,7 @@
                     pass
 
             msg = Ieee8021pMapperServiceProfileFrame(
-                self._ieee_mapper_service_profile_entity_id,  # 802.1p mapper Service Mapper Profile ID
+                self._ieee_mapper_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # 802.1p mapper Service Mapper Profile ID
                 interwork_tp_pointers=gem_entity_ids  # Interworking TP IDs
             )
             frame = msg.set()
@@ -381,7 +388,7 @@
             )
 
             msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                self._mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
                 attributes=attributes
             )
 
@@ -399,7 +406,7 @@
             )
 
             msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                self._mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
                 attributes=attributes
             )
 
@@ -440,7 +447,7 @@
             )
 
             msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                self._mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
                 attributes=attributes
             )
 
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py
index cf2e112..5413a8f 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py
@@ -34,7 +34,7 @@
     task_priority = 200
     name = "Broadcom VLAN Filter Task"
 
-    def __init__(self, omci_agent, device_id, set_vlan_id, priority=task_priority):
+    def __init__(self, omci_agent, device_id, uni_port, set_vlan_id, priority=task_priority):
         """
         Class initialization
 
@@ -43,12 +43,16 @@
         :param set_vlan_id: (int) VLAN to filter for and set
         :param priority: (int) OpenOMCI Task priority (0..255) 255 is the highest
         """
+
+        self.log = structlog.get_logger(device_id=device_id, uni_port=uni_port.port_number)
+
         super(BrcmVlanFilterTask, self).__init__(BrcmVlanFilterTask.name,
                                                  omci_agent,
                                                  device_id,
                                                  priority=priority,
                                                  exclusive=False)
         self._device = omci_agent.get_device(device_id)
+        self._uni_port = uni_port
         self._set_vlan_id = set_vlan_id
         self._results = None
         self._local_deferred = None
@@ -84,7 +88,7 @@
             _mac_bridge_service_profile_entity_id = 0x201
             _mac_bridge_port_ani_entity_id = 0x2102  # TODO: can we just use the entity id from the anis list?
             # Delete bridge ani side vlan filter
-            msg = VlanTaggingFilterDataFrame(_mac_bridge_port_ani_entity_id)
+            msg = VlanTaggingFilterDataFrame(_mac_bridge_port_ani_entity_id + self._uni_port.mac_bridge_port_num)
             frame = msg.delete()
             self.log.debug('openomci-msg', msg=msg)
             self.strobe_watchdog()
@@ -93,7 +97,7 @@
 
             # Re-Create bridge ani side vlan filter
             msg = VlanTaggingFilterDataFrame(
-                _mac_bridge_port_ani_entity_id,  # Entity ID
+                _mac_bridge_port_ani_entity_id + self._uni_port.mac_bridge_port_num,  # Entity ID
                 vlan_tcis=[self._set_vlan_id],  # VLAN IDs
                 forward_operation=0x10
             )
@@ -132,7 +136,7 @@
                 )
             )
             msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                _mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                _mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
                 attributes=attributes  # See above
             )
             frame = msg.set()
@@ -168,7 +172,7 @@
                 )
             )
             msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                _mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                _mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
                 attributes=attributes  # See above
             )
             frame = msg.set()
diff --git a/voltha/adapters/brcm_openomci_onu/onu_gem_port.py b/voltha/adapters/brcm_openomci_onu/onu_gem_port.py
index 571de78..b5e8f60 100644
--- a/voltha/adapters/brcm_openomci_onu/onu_gem_port.py
+++ b/voltha/adapters/brcm_openomci_onu/onu_gem_port.py
@@ -26,7 +26,7 @@
     Broadcom ONU specific implementation
     """
 
-    def __init__(self, gem_id, alloc_id, entity_id,
+    def __init__(self, gem_id, uni_id, alloc_id, entity_id,
                  direction="BIDIRECTIONAL",
                  encryption=False,
                  discard_config=None,
@@ -45,11 +45,12 @@
                  name=None,
                  handler=None):
 
-        self.log = structlog.get_logger(device_id=handler.device_id, gem_id=gem_id)
+        self.log = structlog.get_logger(device_id=handler.device_id, uni_id=uni_id, gem_id=gem_id)
         self.log.debug('function-entry')
 
         self.name = name
         self.gem_id = gem_id
+        self.uni_id = uni_id
         self._alloc_id = alloc_id
         self.tcont_ref = tcont_ref
         self.intf_ref = intf_ref
@@ -255,6 +256,7 @@
         log.debug('function-entry', gem_port=gem_port, entity_id=entity_id)
 
         return OnuGemPort(gem_id=gem_port['gemport_id'],
+                          uni_id=gem_port['uni_id'],
                           alloc_id=gem_port['alloc_id_ref'],
                           entity_id=entity_id,
                           direction=gem_port['direction'],
diff --git a/voltha/adapters/brcm_openomci_onu/onu_tcont.py b/voltha/adapters/brcm_openomci_onu/onu_tcont.py
index 91c3b36..1e22bdb 100644
--- a/voltha/adapters/brcm_openomci_onu/onu_tcont.py
+++ b/voltha/adapters/brcm_openomci_onu/onu_tcont.py
@@ -26,11 +26,12 @@
     """
     Broadcom ONU specific implementation
     """
-    def __init__(self, handler, alloc_id, q_sched_policy, traffic_descriptor):
+    def __init__(self, handler, uni_id, alloc_id, q_sched_policy, traffic_descriptor):
 
-        self.log = structlog.get_logger(device_id=handler.device_id, alloc_id=alloc_id)
+        self.log = structlog.get_logger(device_id=handler.device_id, uni_id=uni_id, alloc_id=alloc_id)
         self.log.debug('function-entry')
 
+        self.uni_id = uni_id
         self.alloc_id = alloc_id
         self._q_sched_policy = 0
         self.q_sched_policy = q_sched_policy
@@ -64,6 +65,7 @@
         log.debug('function-entry', tcont=tcont)
 
         return OnuTCont(handler,
+                        tcont['uni_id'],
                         tcont['alloc-id'],
                         tcont['q_sched_policy'],
                         td
diff --git a/voltha/adapters/brcm_openomci_onu/pon_port.py b/voltha/adapters/brcm_openomci_onu/pon_port.py
index 7fb6a9e..a5ee55d 100644
--- a/voltha/adapters/brcm_openomci_onu/pon_port.py
+++ b/voltha/adapters/brcm_openomci_onu/pon_port.py
@@ -149,12 +149,13 @@
         """
         self.log.debug('function-entry')
 
-        self._port = Port(port_no=self.port_number,
-                          label='PON port',
-                          type=Port.PON_ONU,
-                          admin_state=self._admin_state,
-                          oper_status=self._oper_status,
-                          peers=[])
+        if self._port is None:
+            self._port = Port(port_no=self.port_number,
+                              label='PON port',
+                              type=Port.PON_ONU,
+                              admin_state=self._admin_state,
+                              oper_status=self._oper_status,
+                              peers=[])
         return self._port
 
     def _update_adapter_agent(self):
diff --git a/voltha/adapters/brcm_openomci_onu/uni_port.py b/voltha/adapters/brcm_openomci_onu/uni_port.py
index a887531..c8aca6b 100644
--- a/voltha/adapters/brcm_openomci_onu/uni_port.py
+++ b/voltha/adapters/brcm_openomci_onu/uni_port.py
@@ -35,7 +35,7 @@
 class UniPort(object):
     """Wraps southbound-port(s) support for ONU"""
 
-    def __init__(self, handler, name, port_no, ofp_port_no,
+    def __init__(self, handler, name, uni_id, port_no, ofp_port_no,
                  type=UniType.PPTP):
         self.log = structlog.get_logger(device_id=handler.device_id,
                                         port_no=port_no)
@@ -49,6 +49,7 @@
         self._entity_id = None
         self._mac_bridge_port_num = 0
         self._type = type
+        self._uni_id = uni_id
 
         self._admin_state = AdminState.ENABLED
         self._oper_status = OperStatus.ACTIVE
@@ -57,8 +58,8 @@
         return "UniPort: {}:{}".format(self.name, self.port_number)
 
     @staticmethod
-    def create(handler, name, port_no, ofp_port_no, type):
-        port = UniPort(handler, name, port_no, ofp_port_no, type)
+    def create(handler, name, uni_id, port_no, ofp_port_no, type):
+        port = UniPort(handler, name, uni_id, port_no, ofp_port_no, type)
         return port
 
     def _start(self):
@@ -99,6 +100,15 @@
                 self._stop()
 
     @property
+    def uni_id(self):
+        """
+        Physical prt index on ONU 0 - N
+        :return: (int) uni id
+        """
+        return self._uni_id
+
+
+    @property
     def mac_bridge_port_num(self):
         """
         Port number used when creating MacBridgePortConfigurationDataFrame port number
@@ -178,9 +188,9 @@
         return self._port
 
     def port_id_name(self):
-        return 'uni-{}'.format(self._logical_port_number)
+        return 'uni-{}'.format(self._port_number)
 
-    def add_logical_port(self, openflow_port_no,
+    def add_logical_port(self, openflow_port_no, multi_uni_naming,
                          capabilities=OFPPF_10GB_FD | OFPPF_FIBER,
                          speed=OFPPF_10GB_FD):
 
@@ -209,12 +219,13 @@
             # leave the ports down until omci mib download has finished.  otherwise flows push before time
             openflow_port = ofp_port(
                 port_no=port_no,
-                hw_addr=mac_str_to_tuple('08:00:%02x:%02x:%02x:%02x' %
+                hw_addr=mac_str_to_tuple('08:%02x:%02x:%02x:%02x:%02x' %
                                          ((device.parent_port_no >> 8 & 0xff),
                                           device.parent_port_no & 0xff,
+                                          (port_no >> 16) & 0xff,
                                           (port_no >> 8) & 0xff,
                                           port_no & 0xff)),
-                name=device.serial_number,
+                name=device.serial_number + ['', '-' + str(self._mac_bridge_port_num)][multi_uni_naming],
                 config=0,
                 state=OFPPS_LINK_DOWN,
                 curr=capabilities,
@@ -230,4 +241,4 @@
                                                              device_id=device.id,
                                                              device_port_no=self._port_number))
 
-            self.log.debug('logical-port', openflow_port=openflow_port)
+            self.log.debug('logical-port', id=self.port_id_name(), device_port_no=self._port_number, openflow_port=openflow_port)
diff --git a/voltha/adapters/openolt/openolt_device.py b/voltha/adapters/openolt/openolt_device.py
index 1afac68..0858005 100644
--- a/voltha/adapters/openolt/openolt_device.py
+++ b/voltha/adapters/openolt/openolt_device.py
@@ -213,12 +213,11 @@
         device_info = self.stub.GetDeviceInfo(openolt_pb2.Empty())
         self.log.info('Device connected', device_info=device_info)
 
-        self.platform = self.platform_class(self.log, device_info)
         self.resource_mgr = self.resource_mgr_class(self.device_id,
                                                     self.host_and_port,
                                                     self.extra_args,
                                                     device_info)
-
+        self.platform = self.platform_class(self.log, self.resource_mgr)
         self.flow_mgr = self.flow_mgr_class(self.adapter_agent, self.log,
                                             self.stub, self.device_id,
                                             self.logical_device_id,
@@ -261,16 +260,12 @@
         # Children ports
         child_devices = self.adapter_agent.get_child_devices(self.device_id)
         for onu_device in child_devices:
-            uni_no = self.platform.mk_uni_port_num(
-                onu_device.proxy_address.channel_id,
-                onu_device.proxy_address.onu_id)
-            uni_name = self.port_name(uni_no, Port.ETHERNET_UNI,
-                                      serial_number=onu_device.serial_number)
             onu_adapter_agent = \
                 registry('adapter_loader').get_agent(onu_device.adapter)
             onu_adapter_agent.update_interface(onu_device,
                                                {'oper_state': 'down'})
-            self.onu_ports_down(onu_device, uni_no, uni_name, oper_state)
+            self.onu_ports_down(onu_device, oper_state)
+
         # Children devices
         self.adapter_agent.update_child_devices_state(
             self.device_id, oper_status=oper_state,
@@ -521,13 +516,6 @@
                           expected_onu_id=onu_device.proxy_address.onu_id,
                           received_onu_id=onu_indication.onu_id)
 
-        uni_no = self.platform.mk_uni_port_num(onu_indication.intf_id,
-                                          onu_indication.onu_id)
-        uni_name = self.port_name(uni_no, Port.ETHERNET_UNI,
-                                  serial_number=onu_device.serial_number)
-
-        self.log.debug('port-number-ready', uni_no=uni_no, uni_name=uni_name)
-
         # Admin state
         if onu_indication.admin_state == 'down':
             if onu_indication.oper_state != 'down':
@@ -568,8 +556,7 @@
                 onu_device.oper_status = OperStatus.DISCOVERED
                 self.adapter_agent.update_device(onu_device)
             # Set port oper state to Discovered
-            self.onu_ports_down(onu_device, uni_no, uni_name,
-                                OperStatus.DISCOVERED)
+            self.onu_ports_down(onu_device, OperStatus.DISCOVERED)
 
             onu_adapter_agent.update_interface(onu_device,
                                                {'oper_state': 'down'})
@@ -598,42 +585,37 @@
             self.log.warn('Not-implemented-or-invalid-value-of-oper-state',
                           oper_state=onu_indication.oper_state)
 
-    def onu_ports_down(self, onu_device, uni_no, uni_name, oper_state):
+    def onu_ports_down(self, onu_device, oper_state):
         # Set port oper state to Discovered
         # add port will update port if it exists
-        self.adapter_agent.add_port(
-            self.device_id,
-            Port(
-                port_no=uni_no,
-                label=uni_name,
-                type=Port.ETHERNET_UNI,
-                admin_state=onu_device.admin_state,
-                oper_status=oper_state))
+        # self.adapter_agent.add_port(
+        #    self.device_id,
+        #    Port(
+        #        port_no=uni_no,
+        #        label=uni_name,
+        #        type=Port.ETHERNET_UNI,
+        #        admin_state=onu_device.admin_state,
+        #        oper_status=oper_state))
+        # TODO this should be downning ports in onu adatper
 
         # Disable logical port
         onu_ports = self.proxy.get('devices/{}/ports'.format(onu_device.id))
-        onu_port_id = None
         for onu_port in onu_ports:
-            if onu_port.port_no == uni_no:
-                onu_port_id = onu_port.label
-        if onu_port_id is None:
-            self.log.error('matching-onu-port-label-not-found',
-                           onu_id=onu_device.id, olt_id=self.device_id,
-                           onu_ports=onu_ports)
-            return
-        try:
-            onu_logical_port = self.adapter_agent.get_logical_port(
-                logical_device_id=self.logical_device_id, port_id=onu_port_id)
-            onu_logical_port.ofp_port.state = OFPPS_LINK_DOWN
-            self.adapter_agent.update_logical_port(
-                logical_device_id=self.logical_device_id,
-                port=onu_logical_port)
-            self.log.debug('cascading-oper-state-to-port-and-logical-port')
-        except KeyError as e:
-            self.log.error('matching-onu-port-label-invalid',
-                           onu_id=onu_device.id, olt_id=self.device_id,
-                           onu_ports=onu_ports, onu_port_id=onu_port_id,
-                           error=e)
+            self.log.debug('onu-ports-down', onu_port=onu_port)
+            onu_port_id = onu_port.label
+            try:
+                onu_logical_port = self.adapter_agent.get_logical_port(
+                    logical_device_id=self.logical_device_id, port_id=onu_port_id)
+                onu_logical_port.ofp_port.state = OFPPS_LINK_DOWN
+                self.adapter_agent.update_logical_port(
+                    logical_device_id=self.logical_device_id,
+                    port=onu_logical_port)
+                self.log.debug('cascading-oper-state-to-port-and-logical-port')
+            except KeyError as e:
+                self.log.error('matching-onu-port-label-invalid',
+                               onu_id=onu_device.id, olt_id=self.device_id,
+                               onu_ports=onu_ports, onu_port_id=onu_port_id,
+                               error=e)
 
     def omci_indication(self, omci_indication):
 
@@ -653,22 +635,32 @@
         self.log.debug("packet indication",
                        intf_type=pkt_indication.intf_type,
                        intf_id=pkt_indication.intf_id,
+                       port_no=pkt_indication.port_no,
+                       cookie=pkt_indication.cookie,
                        gemport_id=pkt_indication.gemport_id,
                        flow_id=pkt_indication.flow_id)
 
         if pkt_indication.intf_type == "pon":
-            pon_intf_gemport = (pkt_indication.intf_id, pkt_indication.gemport_id)
-            try:
-                onu_id = int(self.resource_mgr.kv_store[pon_intf_gemport])
-                if onu_id is None:
-                    raise Exception("onu-id-none")
-            except Exception as e:
-                self.log.error("no-onu-reference-for-gem",
-                               gemport_id=pkt_indication.gemport_id, e=e)
-                return
+            if pkt_indication.port_no:
+                logical_port_num = pkt_indication.port_no
+            else:  # TODO Remove this else block after openolt device has been fully rolled out with cookie protobuf change
+                try:
+                    onu_id_uni_id = self.resource_mgr.get_onu_uni_from_ponport_gemport(pkt_indication.intf_id,
+                                                                                       pkt_indication.gemport_id)
+                    onu_id = int(onu_id_uni_id[0])
+                    uni_id = int(onu_id_uni_id[1])
+                    self.log.debug("packet indication-kv", onu_id=onu_id, uni_id=uni_id)
+                    if onu_id is None:
+                        raise Exception("onu-id-none")
+                    if uni_id is None:
+                        raise Exception("uni-id-none")
+                    logical_port_num = self.platform.mk_uni_port_num(pkt_indication.intf_id, onu_id, uni_id)
+                except Exception as e:
+                    self.log.error("no-onu-reference-for-gem",
+                                   gemport_id=pkt_indication.gemport_id, e=e)
+                    return
 
-            logical_port_num = self.platform.mk_uni_port_num(pkt_indication.intf_id,
-                                                        onu_id)
+
         elif pkt_indication.intf_type == "nni":
             logical_port_num = self.platform.intf_id_to_port_no(
                 pkt_indication.intf_id,
@@ -693,7 +685,7 @@
                        packet=str(pkt).encode("HEX"))
 
         # Find port type
-        egress_port_type = self.port_type(egress_port)
+        egress_port_type = self.platform.intf_id_to_port_type_name(egress_port)
         if egress_port_type == Port.ETHERNET_UNI:
 
             if pkt.haslayer(Dot1Q):
@@ -715,11 +707,14 @@
                 'sending-packet-to-ONU', egress_port=egress_port,
                 intf_id=self.platform.intf_id_from_uni_port_num(egress_port),
                 onu_id=self.platform.onu_id_from_port_num(egress_port),
+                uni_id=self.platform.uni_id_from_port_num(egress_port),
+                port_no=egress_port,
                 packet=str(payload).encode("HEX"))
 
             onu_pkt = openolt_pb2.OnuPacket(
                 intf_id=self.platform.intf_id_from_uni_port_num(egress_port),
                 onu_id=self.platform.onu_id_from_port_num(egress_port),
+                port_no=egress_port,
                 pkt=send_pkt)
 
             self.stub.OnuPacketOut(onu_pkt)
@@ -774,7 +769,8 @@
             parent_device_id=self.device_id, parent_port_no=port_no,
             vendor_id=serial_number.vendor_id, proxy_address=proxy_address,
             root=True, serial_number=serial_number_str,
-            admin_state=AdminState.ENABLED)
+            admin_state=AdminState.ENABLED#, **{'vlan':4091} # magic still maps to brcm_openomci_onu.pon_port.BRDCM_DEFAULT_VLAN
+        )
 
     def port_name(self, port_no, port_type, intf_id=None, serial_number=None):
         if port_type is Port.ETHERNET_NNI:
@@ -782,17 +778,7 @@
         elif port_type is Port.PON_OLT:
             return "pon" + str(intf_id)
         elif port_type is Port.ETHERNET_UNI:
-            if serial_number is not None:
-                return serial_number
-            else:
-                return "uni-{}".format(port_no)
-
-    def port_type(self, port_no):
-        ports = self.adapter_agent.get_ports(self.device_id)
-        for port in ports:
-            if port.port_no == port_no:
-                return port.type
-        return None
+            assert False, 'local UNI management not supported'
 
     def add_logical_port(self, port_no, intf_id, oper_state):
         self.log.info('adding-logical-port', port_no=port_no)
@@ -1018,8 +1004,12 @@
             self.log.error('port delete error', error=e)
         serial_number = self.destringify_serial_number(
             child_device.serial_number)
+        # TODO FIXME - For each uni.
+        # TODO FIXME - Flows are not deleted
+        uni_id = 0  # FIXME
         pon_intf_id_onu_id = (child_device.proxy_address.channel_id,
-                              child_device.proxy_address.onu_id)
+                              child_device.proxy_address.onu_id,
+                              uni_id)
         alloc_id = self.resource_mgr.get_alloc_id(pon_intf_id_onu_id)
         # Free any PON resources that were reserved for the ONU
         self.resource_mgr.free_pon_resources_for_onu(pon_intf_id_onu_id)
diff --git a/voltha/adapters/openolt/openolt_flow_mgr.py b/voltha/adapters/openolt/openolt_flow_mgr.py
index 8669d32..1b996e7 100644
--- a/voltha/adapters/openolt/openolt_flow_mgr.py
+++ b/voltha/adapters/openolt/openolt_flow_mgr.py
@@ -194,10 +194,11 @@
                 if field.type == fd.VLAN_VID:
                     classifier_info[METADATA] = field.vlan_vid & 0xfff
 
-        (intf_id, onu_id) = self.platform.extract_access_from_flow(
+        self.log.debug('flow-ports', classifier_inport=classifier_info[IN_PORT], action_output=action_info[OUTPUT])
+        (port_no, intf_id, onu_id, uni_id) = self.platform.extract_access_from_flow(
             classifier_info[IN_PORT], action_info[OUTPUT])
 
-        self.divide_and_add_flow(intf_id, onu_id, classifier_info,
+        self.divide_and_add_flow(intf_id, onu_id, uni_id, port_no, classifier_info,
                                  action_info, flow)
 
     def _is_uni_port(self, port_no):
@@ -251,14 +252,14 @@
             child_device = self.adapter_agent.get_device(child_device_id)
             pon_intf = child_device.proxy_address.channel_id
             onu_id = child_device.proxy_address.onu_id
-            flows = self.resource_mgr.get_flow_id_info(pon_intf, onu_id, flow_id)
+            uni_id = self.platform.uni_id_from_port_num(uni_port_no) if uni_port_no is not None else None
+            flows = self.resource_mgr.get_flow_id_info(pon_intf, onu_id, uni_id, flow_id)
             assert (isinstance(flows, list))
             self.log.debug("retrieved-flows", flows=flows)
             for idx in range(len(flows)):
                 if flow_direction == flows[idx]['flow_type']:
                     flows.pop(idx)
-                    self.update_flow_info_to_kv_store(pon_intf, onu_id,
-                                                      flow_id, flows)
+                    self.update_flow_info_to_kv_store(pon_intf, onu_id, uni_id, flow_id, flows)
                     if len(flows) > 0:
                         # There are still flows referencing the same flow_id.
                         # So the flow should not be freed yet.
@@ -266,7 +267,7 @@
                         # between DS and US.
                         return
 
-            self.resource_mgr.free_flow_id(pon_intf, onu_id, flow_id)
+            self.resource_mgr.free_flow_id_for_uni(pon_intf, onu_id, uni_id, flow_id)
         else:
             self.log.error("invalid-info", uni_port_no=uni_port_no,
                            flow_category=flow_category,
@@ -318,28 +319,27 @@
             self.log.debug('no device flow to remove for this flow (normal '
                            'for multi table flows)', flow=flow)
 
-    def _get_ofp_port_name(self, intf_id, onu_id):
+    def _get_ofp_port_name(self, intf_id, onu_id, uni_id):
         parent_port_no = self.platform.intf_id_to_port_no(intf_id, Port.PON_OLT)
         child_device = self.adapter_agent.get_child_device(self.device_id,
                                                            parent_port_no=parent_port_no, onu_id=onu_id)
         if child_device is None:
             self.log.error("could-not-find-child-device",
                            parent_port_no=intf_id, onu_id=onu_id)
-            return None
-        # FIXME: Assumes single UNI for a ONU device which is visible at ONOS
+            return (None, None)
         ports = self.adapter_agent.get_ports(child_device.id, Port.ETHERNET_UNI)
         logical_port = self.adapter_agent.get_logical_port(
-            self.logical_device_id, ports[0].label)
-        ofp_port_name = logical_port.ofp_port.name
+            self.logical_device_id, ports[uni_id].label)
+        ofp_port_name = (logical_port.ofp_port.name, logical_port.ofp_port.port_no)
         return ofp_port_name
 
-    def divide_and_add_flow(self, intf_id, onu_id, classifier,
+    def divide_and_add_flow(self, intf_id, onu_id, uni_id, port_no, classifier,
                             action, flow):
 
-        self.log.debug('sorting flow', intf_id=intf_id, onu_id=onu_id,
+        self.log.debug('sorting flow', intf_id=intf_id, onu_id=onu_id, uni_id=uni_id, port_no=port_no,
                        classifier=classifier, action=action)
 
-        alloc_id, gem_ports = self.create_tcont_gemport(intf_id, onu_id,
+        alloc_id, gem_ports = self.create_tcont_gemport(intf_id, onu_id, uni_id,
                                                         flow.table_id)
         if alloc_id is None or gem_ports is None:
             self.log.error("alloc-id-gem-ports-unavailable", alloc_id=alloc_id,
@@ -355,7 +355,7 @@
             if IP_PROTO in classifier:
                 if classifier[IP_PROTO] == 17:
                     self.log.debug('dhcp flow add')
-                    self.add_dhcp_trap(intf_id, onu_id, classifier,
+                    self.add_dhcp_trap(intf_id, onu_id, uni_id, port_no, classifier,
                                        action, flow, alloc_id, gemport_id)
                 elif classifier[IP_PROTO] == 2:
                     self.log.warn('igmp flow add ignored, not implemented yet')
@@ -366,19 +366,19 @@
             elif ETH_TYPE in classifier:
                 if classifier[ETH_TYPE] == EAP_ETH_TYPE:
                     self.log.debug('eapol flow add')
-                    self.add_eapol_flow(intf_id, onu_id, flow, alloc_id,
+                    self.add_eapol_flow(intf_id, onu_id, uni_id, port_no, flow, alloc_id,
                                         gemport_id)
                     vlan_id = self.get_subscriber_vlan(fd.get_in_port(flow))
                     if vlan_id is not None:
                         self.add_eapol_flow(
-                            intf_id, onu_id, flow, alloc_id, gemport_id,
+                            intf_id, onu_id, uni_id, port_no, flow, alloc_id, gemport_id,
                             eapol_flow_category=EAPOL_SECONDARY_FLOW,
                             vlan_id=vlan_id)
                     parent_port_no = self.platform.intf_id_to_port_no(intf_id, Port.PON_OLT)
                     onu_device = self.adapter_agent.get_child_device(self.device_id,
                                                                      onu_id=onu_id,
                                                                      parent_port_no=parent_port_no)
-                    ofp_port_name = self._get_ofp_port_name(intf_id, onu_id)
+                    (ofp_port_name, ofp_port_no) = self._get_ofp_port_name(intf_id, onu_id, uni_id)
                     if ofp_port_name is None:
                         self.log.error("port-name-not-found")
                         return
@@ -394,7 +394,7 @@
 
                     self.log.debug('Load-tech-profile-request-to-brcm-handler',
                                    tp_path=tp_path)
-                    msg = {'proxy_address': onu_device.proxy_address,
+                    msg = {'proxy_address': onu_device.proxy_address, 'uni_id': uni_id,
                            'event': 'download_tech_profile', 'event_data': tp_path}
 
                     # Send the event message to the ONU adapter
@@ -403,23 +403,23 @@
 
                 if classifier[ETH_TYPE] == LLDP_ETH_TYPE:
                     self.log.debug('lldp flow add')
-                    self.add_lldp_flow(flow)
+                    self.add_lldp_flow(flow, port_no)
 
             elif PUSH_VLAN in action:
-                self.add_upstream_data_flow(intf_id, onu_id, classifier,
+                self.add_upstream_data_flow(intf_id, onu_id, uni_id, port_no, classifier,
                                             action, flow, alloc_id, gemport_id)
             elif POP_VLAN in action:
-                self.add_downstream_data_flow(intf_id, onu_id, classifier,
+                self.add_downstream_data_flow(intf_id, onu_id, uni_id, port_no, classifier,
                                               action, flow, alloc_id, gemport_id)
             else:
                 self.log.debug('Invalid-flow-type-to-handle',
                                classifier=classifier,
                                action=action, flow=flow)
 
-    def create_tcont_gemport(self, intf_id, onu_id, table_id):
+    def create_tcont_gemport(self, intf_id, onu_id, uni_id, table_id):
         alloc_id, gem_port_ids = None, None
         try:
-            ofp_port_name = self._get_ofp_port_name(intf_id, onu_id)
+            (ofp_port_name, ofp_port_no) = self._get_ofp_port_name(intf_id, onu_id, uni_id)
             if ofp_port_name is None:
                 self.log.error("port-name-not-found")
                 return alloc_id, gem_port_ids
@@ -452,6 +452,8 @@
 
                     self.stub.CreateTconts(openolt_pb2.Tconts(intf_id=intf_id,
                                                               onu_id=onu_id,
+                                                              uni_id=uni_id,
+                                                              port_no=ofp_port_no,
                                                               tconts=tconts))
                 else:
                     raise Exception('Tech-profile-instance-creation-failed')
@@ -471,8 +473,8 @@
         except BaseException as e:
             self.log.exception(exception=e)
 
-        # Update the allocated alloc_id and gem_port_id for the ONU to KV store
-        pon_intf_onu_id = (intf_id, onu_id)
+        # Update the allocated alloc_id and gem_port_id for the ONU/UNI to KV store
+        pon_intf_onu_id = (intf_id, onu_id, uni_id)
         self.resource_mgr.resource_mgrs[intf_id].update_alloc_ids_for_onu(
             pon_intf_onu_id,
             list([alloc_id])
@@ -483,65 +485,67 @@
         )
 
         self.resource_mgr.update_gemports_ponport_to_onu_map_on_kv_store(
-            gem_port_ids, intf_id, onu_id
+            gem_port_ids, intf_id, onu_id, uni_id
         )
 
         return alloc_id, gem_port_ids
 
-    def add_upstream_data_flow(self, intf_id, onu_id, uplink_classifier,
+    def add_upstream_data_flow(self, intf_id, onu_id, uni_id, port_no, uplink_classifier,
                                uplink_action, logical_flow, alloc_id,
                                gemport_id):
 
         uplink_classifier[PACKET_TAG_TYPE] = SINGLE_TAG
 
-        self.add_hsia_flow(intf_id, onu_id, uplink_classifier,
+        self.add_hsia_flow(intf_id, onu_id, uni_id, port_no, uplink_classifier,
                            uplink_action, UPSTREAM,
                            logical_flow, alloc_id, gemport_id)
 
         # Secondary EAP on the subscriber vlan
-        (eap_active, eap_logical_flow) = self.is_eap_enabled(intf_id, onu_id)
+        (eap_active, eap_logical_flow) = self.is_eap_enabled(intf_id, onu_id, uni_id)
         if eap_active:
-            self.add_eapol_flow(intf_id, onu_id, eap_logical_flow, alloc_id,
+            self.add_eapol_flow(intf_id, onu_id, uni_id, port_no, eap_logical_flow, alloc_id,
                                 gemport_id, eapol_flow_category=EAPOL_SECONDARY_FLOW,
                                 vlan_id=uplink_classifier[VLAN_VID])
 
-    def add_downstream_data_flow(self, intf_id, onu_id, downlink_classifier,
+    def add_downstream_data_flow(self, intf_id, onu_id, uni_id, port_no, downlink_classifier,
                                  downlink_action, flow, alloc_id, gemport_id):
         downlink_classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
         # Needed ???? It should be already there
         downlink_action[POP_VLAN] = True
         downlink_action[VLAN_VID] = downlink_classifier[VLAN_VID]
 
-        self.add_hsia_flow(intf_id, onu_id, downlink_classifier,
+        self.add_hsia_flow(intf_id, onu_id, uni_id, port_no, downlink_classifier,
                            downlink_action, DOWNSTREAM,
                            flow, alloc_id, gemport_id)
 
-    def add_hsia_flow(self, intf_id, onu_id, classifier, action,
+    def add_hsia_flow(self, intf_id, onu_id, uni_id, port_no, classifier, action,
                       direction, logical_flow, alloc_id, gemport_id):
 
-        flow_id = self.resource_mgr.get_hsia_flow_for_onu(intf_id, onu_id, gemport_id)
+        flow_id = self.resource_mgr.get_hsia_flow_for_uni(intf_id, onu_id, uni_id, gemport_id)
         if flow_id is None:
             self.log.error("hsia-flow-unavailable")
             return
 
         flow = openolt_pb2.Flow(
-            access_intf_id=intf_id, onu_id=onu_id, flow_id=flow_id,
+            access_intf_id=intf_id, onu_id=onu_id, uni_id=uni_id, flow_id=flow_id,
             flow_type=direction, alloc_id=alloc_id, network_intf_id=0,
             gemport_id=gemport_id,
             classifier=self.mk_classifier(classifier),
             action=self.mk_action(action),
-            priority=logical_flow.priority)
+            priority=logical_flow.priority,
+            port_no=port_no,
+            cookie=logical_flow.cookie)
 
         if self.add_flow_to_device(flow, logical_flow):
             flow_info = self._get_flow_info_as_json_blob(flow, HSIA_FLOW)
-            self.update_flow_info_to_kv_store(flow.access_intf_id, flow.onu_id,
+            self.update_flow_info_to_kv_store(flow.access_intf_id, flow.onu_id, flow.uni_id,
                                               flow.flow_id, flow_info)
 
-    def add_dhcp_trap(self, intf_id, onu_id, classifier, action, logical_flow,
+    def add_dhcp_trap(self, intf_id, onu_id, uni_id, port_no, classifier, action, logical_flow,
                       alloc_id, gemport_id):
 
         self.log.debug('add dhcp upstream trap', classifier=classifier,
-                       intf_id=intf_id, onu_id=onu_id, action=action)
+                       intf_id=intf_id, onu_id=onu_id, uni_id=uni_id, action=action)
 
         action.clear()
         action[TRAP_TO_HOST] = True
@@ -550,25 +554,27 @@
         classifier[PACKET_TAG_TYPE] = SINGLE_TAG
         classifier.pop(VLAN_VID, None)
 
-        pon_intf_onu_id = (intf_id, onu_id)
-        flow_id = self.resource_mgr.get_flow_id(pon_intf_onu_id)
+        flow_id = self.resource_mgr.get_flow_id(intf_id, onu_id, uni_id)
 
         dhcp_flow = openolt_pb2.Flow(
-            onu_id=onu_id, flow_id=flow_id, flow_type=UPSTREAM,
+            onu_id=onu_id, uni_id=uni_id, flow_id=flow_id, flow_type=UPSTREAM,
             access_intf_id=intf_id, gemport_id=gemport_id,
             alloc_id=alloc_id, network_intf_id=0,
             priority=logical_flow.priority,
             classifier=self.mk_classifier(classifier),
-            action=self.mk_action(action))
+            action=self.mk_action(action),
+            port_no=port_no,
+            cookie=logical_flow.cookie)
 
         if self.add_flow_to_device(dhcp_flow, logical_flow):
             flow_info = self._get_flow_info_as_json_blob(dhcp_flow, DHCP_FLOW)
             self.update_flow_info_to_kv_store(dhcp_flow.access_intf_id,
                                               dhcp_flow.onu_id,
+                                              dhcp_flow.uni_id,
                                               dhcp_flow.flow_id,
                                               flow_info)
 
-    def add_eapol_flow(self, intf_id, onu_id, logical_flow, alloc_id,
+    def add_eapol_flow(self, intf_id, onu_id, uni_id, port_no, logical_flow, alloc_id,
                        gemport_id, eapol_flow_category=EAPOL_PRIMARY_FLOW,
                        vlan_id=DEFAULT_MGMT_VLAN):
 
@@ -580,20 +586,21 @@
         uplink_action = dict()
         uplink_action[TRAP_TO_HOST] = True
 
-        pon_intf_onu_id = (intf_id, onu_id)
         # Add Upstream EAPOL Flow.
         if eapol_flow_category == EAPOL_PRIMARY_FLOW:
-            uplink_flow_id = self.resource_mgr.get_flow_id(pon_intf_onu_id)
+            uplink_flow_id = self.resource_mgr.get_flow_id(intf_id, onu_id, uni_id)
         else:
-            uplink_flow_id = self.resource_mgr.get_flow_id(pon_intf_onu_id)
+            uplink_flow_id = self.resource_mgr.get_flow_id(intf_id, onu_id, uni_id)
 
         upstream_flow = openolt_pb2.Flow(
-            access_intf_id=intf_id, onu_id=onu_id, flow_id=uplink_flow_id,
+            access_intf_id=intf_id, onu_id=onu_id, uni_id=uni_id, flow_id=uplink_flow_id,
             flow_type=UPSTREAM, alloc_id=alloc_id, network_intf_id=0,
             gemport_id=gemport_id,
             classifier=self.mk_classifier(uplink_classifier),
             action=self.mk_action(uplink_action),
-            priority=logical_flow.priority)
+            priority=logical_flow.priority,
+            port_no=port_no,
+            cookie=logical_flow.cookie)
 
         logical_flow = copy.deepcopy(logical_flow)
         logical_flow.match.oxm_fields.extend(fd.mk_oxm_fields([fd.vlan_vid(
@@ -606,6 +613,7 @@
                                                              EAPOL_PRIMARY_FLOW)
                 self.update_flow_info_to_kv_store(upstream_flow.access_intf_id,
                                                   upstream_flow.onu_id,
+                                                  upstream_flow.uni_id,
                                                   upstream_flow.flow_id,
                                                   flow_info)
             else:
@@ -613,13 +621,16 @@
                                                              EAPOL_SECONDARY_FLOW)
                 self.update_flow_info_to_kv_store(upstream_flow.access_intf_id,
                                                   upstream_flow.onu_id,
+                                                  upstream_flow.uni_id,
                                                   upstream_flow.flow_id,
                                                   flow_info)
 
         if vlan_id == DEFAULT_MGMT_VLAN:
             # Add Downstream EAPOL Flow, Only for first EAP flow (BAL
             # requirement)
-            special_vlan_downstream_flow = 4000 - onu_id
+            # FIXME this is actually not used. But, here to be consistent with the upstream tagging from ONU.
+            # It needs refactored to be completely removed
+            special_vlan_downstream_flow = 4091 # 4000 - onu_id
 
             downlink_classifier = dict()
             downlink_classifier[PACKET_TAG_TYPE] = SINGLE_TAG
@@ -629,16 +640,17 @@
             downlink_action[PUSH_VLAN] = True
             downlink_action[VLAN_VID] = vlan_id
 
-            pon_intf_onu_id = (intf_id, onu_id)
-            downlink_flow_id = self.resource_mgr.get_flow_id(pon_intf_onu_id)
+            downlink_flow_id = self.resource_mgr.get_flow_id(intf_id, onu_id, uni_id)
 
             downstream_flow = openolt_pb2.Flow(
-                access_intf_id=intf_id, onu_id=onu_id, flow_id=downlink_flow_id,
+                access_intf_id=intf_id, onu_id=onu_id, uni_id=uni_id, flow_id=downlink_flow_id,
                 flow_type=DOWNSTREAM, alloc_id=alloc_id, network_intf_id=0,
                 gemport_id=gemport_id,
                 classifier=self.mk_classifier(downlink_classifier),
                 action=self.mk_action(downlink_action),
-                priority=logical_flow.priority)
+                priority=logical_flow.priority,
+                port_no=port_no,
+                cookie=logical_flow.cookie)
 
             downstream_logical_flow = ofp_flow_stats(
                 id=logical_flow.id, cookie=logical_flow.cookie,
@@ -652,13 +664,14 @@
 
             downstream_logical_flow.instructions.extend(
                 fd.mk_instructions_from_actions([fd.output(
-                    self.platform.mk_uni_port_num(intf_id, onu_id))]))
+                    self.platform.mk_uni_port_num(intf_id, onu_id, uni_id))]))
 
             if self.add_flow_to_device(downstream_flow, downstream_logical_flow):
                 flow_info = self._get_flow_info_as_json_blob(downstream_flow,
                                                              EAPOL_PRIMARY_FLOW)
                 self.update_flow_info_to_kv_store(downstream_flow.access_intf_id,
                                                   downstream_flow.onu_id,
+                                                  downstream_flow.uni_id,
                                                   downstream_flow.flow_id,
                                                   flow_info)
 
@@ -673,7 +686,7 @@
                 if logical_flow.id not in logical_flows_ids_provisioned:
                     self.add_flow(logical_flow)
             except Exception as e:
-                self.log.debug('Problem readding this flow', error=e)
+                self.log.exception('Problem reading this flow', e=e)
 
     def reset_flows(self):
         self.flows_proxy.update('/', Flows())
@@ -681,7 +694,7 @@
     """ Add a downstream LLDP trap flow on the NNI interface
     """
 
-    def add_lldp_flow(self, logical_flow, network_intf_id=0):
+    def add_lldp_flow(self, logical_flow, port_no, network_intf_id=0):
 
         classifier = dict()
         classifier[ETH_TYPE] = LLDP_ETH_TYPE
@@ -701,24 +714,27 @@
         # we need to have a re-look at this.
         # *********************************************
         onu_id = -1
-        intf_id_onu_id = (network_intf_id, onu_id)
-        flow_id = self.resource_mgr.get_flow_id(intf_id_onu_id)
+        uni_id = -1
+        flow_id = self.resource_mgr.get_flow_id(network_intf_id, onu_id, uni_id)
 
         downstream_flow = openolt_pb2.Flow(
             access_intf_id=-1,  # access_intf_id not required
-            onu_id=onu_id,  # onu_id not required
+            onu_id=onu_id, # onu_id not required
+            uni_id=uni_id, # uni_id not used
             flow_id=flow_id,
             flow_type=DOWNSTREAM,
             network_intf_id=network_intf_id,
             gemport_id=-1,  # gemport_id not required
             classifier=self.mk_classifier(classifier),
             action=self.mk_action(action),
-            priority=logical_flow.priority)
+            priority=logical_flow.priority,
+            port_no=port_no,
+            cookie=logical_flow.cookie)
 
         self.log.debug('add lldp downstream trap', classifier=classifier,
-                       action=action, flow=downstream_flow)
+                       action=action, flow=downstream_flow, port_no=port_no)
         if self.add_flow_to_device(downstream_flow, logical_flow):
-            self.update_flow_info_to_kv_store(network_intf_id, onu_id,
+            self.update_flow_info_to_kv_store(network_intf_id, onu_id, uni_id,
                                               flow_id, downstream_flow)
 
     def mk_classifier(self, classifier_info):
@@ -771,13 +787,14 @@
             return
         return action
 
-    def is_eap_enabled(self, intf_id, onu_id):
+    def is_eap_enabled(self, intf_id, onu_id, uni_id):
         flows = self.logical_flows_proxy.get('/').items
 
         for flow in flows:
             eap_flow = False
             eap_intf_id = None
             eap_onu_id = None
+            eap_uni_id = None
             for field in fd.get_ofb_fields(flow):
                 if field.type == fd.ETH_TYPE:
                     if field.eth_type == EAP_ETH_TYPE:
@@ -786,12 +803,14 @@
                     eap_intf_id = self.platform.intf_id_from_uni_port_num(
                         field.port)
                     eap_onu_id = self.platform.onu_id_from_port_num(field.port)
+                    eap_uni_id = self.platform.uni_id_from_port_num(field.port)
 
             if eap_flow:
-                self.log.debug('eap flow detected', onu_id=onu_id,
+                self.log.debug('eap flow detected', onu_id=onu_id, uni_id=uni_id,
                                intf_id=intf_id, eap_intf_id=eap_intf_id,
-                               eap_onu_id=eap_onu_id)
-            if eap_flow and intf_id == eap_intf_id and onu_id == eap_onu_id:
+                               eap_onu_id=eap_onu_id,
+                               eap_uni_id=eap_uni_id)
+            if eap_flow and intf_id == eap_intf_id and onu_id == eap_onu_id and uni_id == eap_uni_id:
                 return True, flow
 
         return False, None
@@ -832,9 +851,8 @@
             self.register_flow(logical_flow, flow)
             return True
 
-    def update_flow_info_to_kv_store(self, intf_id, onu_id, flow_id, flow):
-        pon_intf_onu_id = (intf_id, onu_id)
-        self.resource_mgr.update_flow_id_info_for_onu(pon_intf_onu_id,
+    def update_flow_info_to_kv_store(self, intf_id, onu_id, uni_id, flow_id, flow):
+        self.resource_mgr.update_flow_id_info_for_uni(intf_id, onu_id, uni_id,
                                                       flow_id, flow)
 
     def register_flow(self, logical_flow, device_flow):
@@ -894,8 +912,11 @@
 
     def clear_flows_and_scheduler_for_logical_port(self, child_device, logical_port):
         ofp_port_name = logical_port.ofp_port.name
+        port_no = logical_port.ofp_port.port_no
         pon_port = child_device.proxy_address.channel_id
         onu_id = child_device.proxy_address.onu_id
+        uni_id = self.platform.uni_id_from_port_num(logical_port)
+
         # TODO: The DEFAULT_TECH_PROFILE_ID is assumed. Right way to do,
         # is probably to maintain a list of Tech-profile table IDs associated
         # with the UNI logical_port. This way, when the logical port is deleted,
@@ -905,13 +926,10 @@
             get_tech_profile_instance(
             DEFAULT_TECH_PROFILE_TABLE_ID,
             ofp_port_name)
-        flow_ids = self.resource_mgr.get_current_flow_ids_for_onu(pon_port,
-                                                                  onu_id)
+        flow_ids = self.resource_mgr.get_current_flow_ids_for_uni(pon_port, onu_id, uni_id)
         self.log.debug("outstanding-flows-to-be-cleared", flow_ids=flow_ids)
         for flow_id in flow_ids:
-            flow_infos = self.resource_mgr.get_flow_id_info(pon_port,
-                                                            onu_id,
-                                                            flow_id)
+            flow_infos = self.resource_mgr.get_flow_id_info(pon_port, onu_id, uni_id, flow_id)
             for flow_info in flow_infos:
                 direction = flow_info['flow_type']
                 flow_to_remove = openolt_pb2.Flow(flow_id=flow_id,
@@ -926,12 +944,14 @@
                     else:
                         raise grpc_e
 
-                self.resource_mgr.free_flow_id(pon_port, onu_id, flow_id)
+                self.resource_mgr.free_flow_id_for_uni(pon_port, onu_id, uni_id, flow_id)
 
         try:
             tconts = self.tech_profile[pon_port].get_tconts(tech_profile_instance)
             self.stub.RemoveTconts(openolt_pb2.Tconts(intf_id=pon_port,
                                                       onu_id=onu_id,
+                                                      uni_id=uni_id,
+                                                      port_no=port_no,
                                                       tconts=tconts))
         except grpc.RpcError as grpc_e:
             self.log.error('error-removing-tcont-scheduler-queues',
@@ -970,7 +990,7 @@
         self.log.debug("flow-info", json_blob=json_blob)
         json_blob['flow_category'] = flow_category
         flow_info = self.resource_mgr.get_flow_id_info(flow.access_intf_id,
-                                                       flow.onu_id, flow.flow_id)
+                                                       flow.onu_id, flow.uni_id, flow.flow_id)
 
         if flow_info is None:
             flow_info = list()
diff --git a/voltha/adapters/openolt/openolt_platform.py b/voltha/adapters/openolt/openolt_platform.py
index c62bf8b..a44eafc 100644
--- a/voltha/adapters/openolt/openolt_platform.py
+++ b/voltha/adapters/openolt/openolt_platform.py
@@ -76,21 +76,27 @@
 
 """
 
-# MAX_ONUS_PER_PON = 112
-MAX_ONUS_PER_PON = 32
-
 class OpenOltPlatform(object):
+    MAX_PONS_PER_OLT = 16
+    MAX_ONUS_PER_PON = 32
+    MAX_UNIS_PER_ONU = 16
 
-    def __init__(self, log, device_info):
+    def __init__(self, log, resource_mgr):
         self.log = log
-        self.device_info = device_info
+        self.resource_mgr = resource_mgr
 
-    def mk_uni_port_num(self, intf_id, onu_id):
-        return intf_id << 11 | onu_id << 4
+    def mk_uni_port_num(self, intf_id, onu_id, uni_id):
+        assert intf_id < OpenOltPlatform.MAX_PONS_PER_OLT
+        assert onu_id < OpenOltPlatform.MAX_ONUS_PER_PON
+        assert uni_id < OpenOltPlatform.MAX_UNIS_PER_ONU
+        self.resource_mgr.assert_uni_id_limit(intf_id, onu_id, uni_id)
+        return intf_id << 11 | onu_id << 4 | uni_id
 
-    def mk_flow_id(self, intf_id, onu_id, idx):
-        return intf_id << 9 | onu_id << 4 | idx
+    #def mk_flow_id(self, intf_id, onu_id, idx):
+    #    return intf_id << 9 | onu_id << 4 | idx
 
+    def uni_id_from_port_num(self, port_num):
+        return port_num & 0xF
 
     def onu_id_from_port_num(self, port_num):
         return (port_num >> 4) & 0x7F
@@ -123,7 +129,7 @@
         elif intf_id & (0x1 << 16) == (0x1 << 16):
             return Port.ETHERNET_NNI
         else:
-            return None
+            return Port.ETHERNET_UNI
 
     def port_type_name_by_port_index(self, port_index):
         try:
@@ -133,11 +139,15 @@
 
     def extract_access_from_flow(self, in_port, out_port):
         if self.is_upstream(out_port):
-            return (self.intf_id_from_uni_port_num(in_port),
-                    self.onu_id_from_port_num(in_port))
+            return (in_port,
+                    self.intf_id_from_uni_port_num(in_port),
+                    self.onu_id_from_port_num(in_port),
+                    self.uni_id_from_port_num(in_port))
         else:
-            return (self.intf_id_from_uni_port_num(out_port),
-                    self.onu_id_from_port_num(out_port))
+            return (out_port,
+                    self.intf_id_from_uni_port_num(out_port),
+                    self.onu_id_from_port_num(out_port),
+                    self.uni_id_from_port_num(out_port))
 
     def is_upstream(self, out_port):
 
@@ -149,6 +159,6 @@
             return True
 
         return False
-
-    def max_onus_per_pon(self):
-        return MAX_ONUS_PER_PON
+    #
+    #def max_onus_per_pon(self):
+    #    return OpenOltPlatform.MAX_ONUS_PER_PON
diff --git a/voltha/adapters/openolt/openolt_resource_manager.py b/voltha/adapters/openolt/openolt_resource_manager.py
index 7e869d4..49b353d 100644
--- a/voltha/adapters/openolt/openolt_resource_manager.py
+++ b/voltha/adapters/openolt/openolt_resource_manager.py
@@ -23,6 +23,7 @@
 from voltha.adapters.openolt.openolt_flow_mgr import *
 
 from voltha.adapters.openolt.protos import openolt_pb2
+from voltha.adapters.openolt.openolt_platform import OpenOltPlatform
 
 
 class OpenOltResourceMgr(object):
@@ -35,6 +36,7 @@
         self.host_and_port = host_and_port
         self.extra_args = extra_args
         self.device_info = device_info
+        self.max_uni_id_per_onu = 0 #OpenOltPlatform.MAX_UNIS_PER_ONU, Uncomment or override to make default multi-uni
         self.args = registry('main').get_args()
 
         # KV store's IP Address and PORT
@@ -113,6 +115,17 @@
         for key, resource_mgr in self.resource_mgrs.iteritems():
             resource_mgr.clear_device_resource_pool()
 
+    def assert_pon_id_limit(self, pon_intf_id):
+        assert pon_intf_id in self.resource_mgrs
+
+    def assert_onu_id_limit(self, pon_intf_id, onu_id):
+        self.assert_pon_id_limit(pon_intf_id)
+        self.resource_mgrs[pon_intf_id].assert_resource_limits(onu_id, PONResourceManager.ONU_ID)
+
+    def assert_uni_id_limit(self, pon_intf_id, onu_id, uni_id):
+        self.assert_onu_id_limit(pon_intf_id, onu_id)
+        self.resource_mgrs[pon_intf_id].assert_resource_limits(uni_id, PONResourceManager.UNI_ID)
+
     def get_onu_id(self, pon_intf_id):
         onu_id = self.resource_mgrs[pon_intf_id].get_resource_id(
             pon_intf_id, PONResourceManager.ONU_ID, 1)
@@ -124,36 +137,36 @@
 
         return onu_id
 
-    def get_flow_id(self, pon_intf_onu_id):
-        pon_intf = pon_intf_onu_id[0]
-        flow_id = self.resource_mgrs[pon_intf].get_resource_id(
-            pon_intf_onu_id[0], PONResourceManager.FLOW_ID)
+    def get_flow_id(self, pon_intf_id, onu_id, uni_id):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
+        flow_id = self.resource_mgrs[pon_intf_id].get_resource_id(
+            pon_intf_id, PONResourceManager.FLOW_ID)
         if flow_id is not None:
-            self.resource_mgrs[pon_intf].update_flow_id_for_onu(pon_intf_onu_id, flow_id)
+            self.resource_mgrs[pon_intf_id].update_flow_id_for_onu(pon_intf_onu_id, flow_id)
 
         return flow_id
 
-    def get_flow_id_info(self, pon_intf_id, onu_id, flow_id):
-        pon_intf_onu_id = (pon_intf_id, onu_id)
+    def get_flow_id_info(self, pon_intf_id, onu_id, uni_id, flow_id):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
         return self.resource_mgrs[pon_intf_id].get_flow_id_info(pon_intf_onu_id, flow_id)
 
-    def get_current_flow_ids_for_onu(self, pon_intf_id, onu_id):
-        pon_intf_onu_id = (pon_intf_id, onu_id)
+    def get_current_flow_ids_for_uni(self, pon_intf_id, onu_id, uni_id):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
         return self.resource_mgrs[pon_intf_id].get_current_flow_ids_for_onu(pon_intf_onu_id)
 
-    def update_flow_id_info_for_onu(self, pon_intf_onu_id, flow_id, flow_data):
-        pon_intf_id = pon_intf_onu_id[0]
+    def update_flow_id_info_for_uni(self, pon_intf_id, onu_id, uni_id, flow_id, flow_data):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
         return self.resource_mgrs[pon_intf_id].update_flow_id_info_for_onu(
             pon_intf_onu_id, flow_id, flow_data)
 
-    def get_hsia_flow_for_onu(self, pon_intf_id, onu_id, gemport_id):
-        pon_intf_onu_id = (pon_intf_id, onu_id)
+    def get_hsia_flow_for_uni(self, pon_intf_id, onu_id, uni_id, gemport_id):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
         try:
             flow_ids = self.resource_mgrs[pon_intf_id]. \
                 get_current_flow_ids_for_onu(pon_intf_onu_id)
             if flow_ids is not None:
                 for flow_id in flow_ids:
-                    flows = self.get_flow_id_info(pon_intf_id, onu_id, flow_id)
+                    flows = self.get_flow_id_info(pon_intf_id, onu_id, uni_id, flow_id)
                     assert (isinstance(flows, list))
                     for flow in flows:
                         if flow['flow_category'] == HSIA_FLOW and \
@@ -162,7 +175,7 @@
         except Exception as e:
             self.log.error("error-retrieving-flow-info", e=e)
 
-        return self.get_flow_id(pon_intf_onu_id)
+        return self.get_flow_id(pon_intf_id, onu_id, uni_id)
 
     def get_alloc_id(self, pon_intf_onu_id):
         # Derive the pon_intf from the pon_intf_onu_id tuple
@@ -194,20 +207,27 @@
 
     def get_current_gemport_ids_for_onu(self, pon_intf_onu_id):
         pon_intf_id = pon_intf_onu_id[0]
+        assert False, 'unused function'
         return self.resource_mgrs[pon_intf_id].get_current_gemport_ids_for_onu(pon_intf_onu_id)
 
-    def update_gemports_ponport_to_onu_map_on_kv_store(self, gemport_list, pon_port, onu_id):
+    def update_gemports_ponport_to_onu_map_on_kv_store(self, gemport_list, pon_port, onu_id, uni_id):
         for gemport in gemport_list:
             pon_intf_gemport = (pon_port, gemport)
             # This information is used when packet_indication is received and
             # we need to derive the ONU Id for which the packet arrived based
             # on the pon_intf and gemport available in the packet_indication
-            self.kv_store[str(pon_intf_gemport)] = str(onu_id)
+            self.kv_store[str(pon_intf_gemport)] = ' '.join(map(str, (onu_id, uni_id)))
+
+    def get_onu_uni_from_ponport_gemport(self, pon_port, gemport):
+        pon_intf_gemport = (pon_port, gemport)
+        return tuple(map(int, self.kv_store[str(pon_intf_gemport)].split(' ')))
 
     def get_gemport_id(self, pon_intf_onu_id, num_of_id=1):
         # Derive the pon_intf and onu_id from the pon_intf_onu_id tuple
         pon_intf = pon_intf_onu_id[0]
         onu_id = pon_intf_onu_id[1]
+        uni_id = pon_intf_onu_id[2]
+        assert False, 'unused function'
 
         gemport_id_list = self.resource_mgrs[pon_intf].get_current_gemport_ids_for_onu(
             pon_intf_onu_id)
@@ -230,7 +250,7 @@
                                                                 gemport_id_list)
 
         self.update_gemports_ponport_to_onu_map_on_kv_store(gemport_id_list,
-                                                            pon_intf, onu_id)
+                                                            pon_intf, onu_id, uni_id)
         return gemport_id_list
 
     def free_onu_id(self, pon_intf_id, onu_id):
@@ -241,10 +261,10 @@
         self.resource_mgrs[pon_intf_id].remove_resource_map(
             pon_intf_onu_id)
 
-    def free_flow_id(self, pon_intf_id, onu_id, flow_id):
+    def free_flow_id_for_uni(self, pon_intf_id, onu_id, uni_id, flow_id):
         self.resource_mgrs[pon_intf_id].free_resource_id(
             pon_intf_id, PONResourceManager.FLOW_ID, flow_id)
-        pon_intf_onu_id = (pon_intf_id, onu_id)
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
         self.resource_mgrs[pon_intf_id].update_flow_id_for_onu(pon_intf_onu_id,
                                                                flow_id, False)
         self.resource_mgrs[pon_intf_id].remove_flow_id_info(pon_intf_onu_id,
@@ -361,6 +381,7 @@
             flow_id_start_idx=flow_id_start,
             flow_id_end_idx=flow_id_end,
             flow_id_shared_pool_id=flow_id_shared_pool_id,
+            uni_id_start_idx=0, uni_id_end_idx=self.max_uni_id_per_onu,
             num_of_pon_ports=self.device_info.pon_ports,
             intf_ids=arange.intf_ids
         )
@@ -388,3 +409,6 @@
                                                   flow_id_end_idx=flow_id_end)
                 resource_mgr.update_ranges(flow_id_start_idx=flow_id_start, flow_id_end_idx=flow_id_end,
                                            flow_id_shared_resource_mgr=global_resource_mgr)
+
+        # Make sure loaded range fits the platform bit encoding ranges
+        resource_mgr.update_ranges(uni_id_start_idx=0, uni_id_end_idx=OpenOltPlatform.MAX_UNIS_PER_ONU-1)
diff --git a/voltha/adapters/openolt/protos/openolt.proto b/voltha/adapters/openolt/protos/openolt.proto
index 8c35bba..0368bd8 100644
--- a/voltha/adapters/openolt/protos/openolt.proto
+++ b/voltha/adapters/openolt/protos/openolt.proto
@@ -217,6 +217,8 @@
     fixed32 intf_id = 1;
     fixed32 gemport_id = 2;
     fixed32 flow_id = 3;
+    fixed32 port_no = 6;
+    fixed64 cookie = 7;
     bytes pkt = 4;
 }
 
@@ -244,6 +246,7 @@
 message OnuPacket {
     fixed32 intf_id = 1;
     fixed32 onu_id = 2;
+    fixed32 port_no = 4;
     bytes pkt = 3;
 }
 
@@ -342,6 +345,7 @@
 message Flow {
     sfixed32 access_intf_id = 1;
     sfixed32 onu_id = 2;
+    sfixed32 uni_id = 11;
     fixed32 flow_id = 3;
     string flow_type = 4;	// upstream, downstream, broadcast, multicast
     sfixed32 alloc_id = 10;
@@ -350,6 +354,8 @@
     Classifier classifier = 7;
     Action action = 8;
     sfixed32 priority = 9;
+    fixed64 cookie = 12; // must be provided for any flow with trap_to_host action. Returned in PacketIndication
+    fixed32 port_no = 13; // must be provided for any flow with trap_to_host action. Returned in PacketIndication
 }
 
 message SerialNumber {
@@ -515,6 +521,8 @@
 message Tconts {
     fixed32 intf_id = 1;
     fixed32 onu_id = 2;
+    fixed32 uni_id = 4;
+    fixed32 port_no = 5;
     repeated Tcont tconts = 3;
 }
 
diff --git a/voltha/core/logical_device_agent.py b/voltha/core/logical_device_agent.py
index f3a8417..70e26b6 100644
--- a/voltha/core/logical_device_agent.py
+++ b/voltha/core/logical_device_agent.py
@@ -786,42 +786,45 @@
             if len(downstream_ports) == 0:
                 return None, None
             # assert len(downstream_ports) == 1
-            flows = OrderedDict((f.id, f) for f in [
-                mk_flow_stat(
-                    priority=500,
-                    match_fields=[
-                        in_port(downstream_ports[0].port_no),
-                        vlan_vid(ofp.OFPVID_PRESENT | 0)
-                    ],
-                    actions=[
-                        set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
-                        output(upstream_ports[0].port_no)
-                    ]
-                ),
-                mk_flow_stat(
-                    priority=500,
-                    match_fields=[
-                        in_port(downstream_ports[0].port_no),
-                        vlan_vid(0)
-                    ],
-                    actions=[
-                        push_vlan(0x8100),
-                        set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
-                        output(upstream_ports[0].port_no)
-                    ]
-                ),
-                mk_flow_stat(
-                    priority=500,
-                    match_fields=[
-                        in_port(upstream_ports[0].port_no),
-                        vlan_vid(ofp.OFPVID_PRESENT | device.vlan)
-                    ],
-                    actions=[
-                        set_field(vlan_vid(ofp.OFPVID_PRESENT | 0)),
-                        output(downstream_ports[0].port_no)
-                    ]
-                ),
-            ])
+            upstream_port  = upstream_ports[0]
+            flows = OrderedDict()
+            for downstream_port in downstream_ports:
+                flows.update(OrderedDict((f.id, f) for f in [
+                    mk_flow_stat(
+                        priority=500,
+                        match_fields=[
+                            in_port(downstream_port.port_no),
+                            vlan_vid(ofp.OFPVID_PRESENT | 0)
+                        ],
+                        actions=[
+                            set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
+                            output(upstream_port.port_no)
+                        ]
+                    ),
+                    mk_flow_stat(
+                        priority=500,
+                        match_fields=[
+                            in_port(downstream_port.port_no),
+                            vlan_vid(0)
+                        ],
+                        actions=[
+                            push_vlan(0x8100),
+                            set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
+                            output(upstream_port.port_no)
+                        ]
+                    ),
+                    mk_flow_stat(
+                        priority=500,
+                        match_fields=[
+                            in_port(upstream_port.port_no),
+                            vlan_vid(ofp.OFPVID_PRESENT | device.vlan)
+                        ],
+                        actions=[
+                            set_field(vlan_vid(ofp.OFPVID_PRESENT | 0)),
+                            output(downstream_port.port_no)
+                        ]
+                    ),
+                ]))
             groups = OrderedDict()
             return flows, groups
 
diff --git a/voltha/extensions/omci/omci_cc.py b/voltha/extensions/omci/omci_cc.py
index 6632d28..1d5c897 100644
--- a/voltha/extensions/omci/omci_cc.py
+++ b/voltha/extensions/omci/omci_cc.py
@@ -373,14 +373,18 @@
                                         status = omci_getnext_msg.fields['success_code']
 
                                         if status != ReasonCodes.Success.value:
-                                            raise Exception('omci-status ' + status)
+                                            raise Exception('get-next-failure table=' + eca.field.name +
+                                                            ' entity_id=' + str(entity_id) +
+                                                            ' sqn=' + str(seq_no) + ' omci-status ' + str(status))
 
                                         break
                                     except Exception as e:
-                                        self.log.exception('get-next-error ' + eca.field.name, e=e)
                                         retries -= 1
                                         if retries <= 0:
+                                            self.log.exception('get-next-error-abort', e=e)
                                             raise e
+                                        else:
+                                            self.log.error('get-next-error-retry', e=e, retries_remaining=retries)
 
                                 # Extract the data
                                 num_octets = count - offset
diff --git a/voltha/extensions/omci/tasks/omci_get_request.py b/voltha/extensions/omci/tasks/omci_get_request.py
index 45c1db4..8b353c9 100644
--- a/voltha/extensions/omci/tasks/omci_get_request.py
+++ b/voltha/extensions/omci/tasks/omci_get_request.py
@@ -47,7 +47,7 @@
     name = "ONU OMCI Get Task"
 
     def __init__(self, omci_agent, device_id, entity_class, entity_id, attributes,
-                 exclusive=False, allow_failure=False):
+                 exclusive=True, allow_failure=False):
         """
         Class initialization
 
@@ -57,7 +57,7 @@
         :param entity_id: (int) ME Class instance ID to retrieve
         :param attributes: (list or set) Name of attributes to retrieve
         :param exclusive: (bool) True if this GET request Task exclusively own the
-                                 OMCI-CC while running. Default: False
+                                 OMCI-CC while running. Default: True
         :param allow_failure: (bool) If true, attempt to get all valid attributes
                                      if the original request receives an error
                                      code of 9 (Attributes failed or unknown).