factory: Add replacement MLB SMT test list

We should write cr50 board ID to replacement MLB before leaving the
factory. The replacement MLB SMT test list will be used by the factory
to make sure we write these info properly during manufacturing.

BUG=b:171467079
TEST=manual test on DUT; make test; test_list_checker.py

Change-Id: I5b28de1a96e796fb0c63be548b1638d223bbeb05
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/factory/+/2610678
Tested-by: Cheng-Han Yang <chenghan@chromium.org>
Reviewed-by: Cheng Yueh <cyueh@chromium.org>
Commit-Queue: Cheng-Han Yang <chenghan@chromium.org>
diff --git a/py/gooftool/commands.py b/py/gooftool/commands.py
index ed235ba..987ce9d 100755
--- a/py/gooftool/commands.py
+++ b/py/gooftool/commands.py
@@ -181,6 +181,10 @@
     '--rma_mode', action='store_true',
     help='Enable RMA mode, do not check for deprecated components.')
 
+_replacement_mlb_mode_cmd_arg = CmdArg(
+    '--replacement_mlb_mode', action='store_true',
+    help='Enable replacement MLB mode, only do cr50 finalize.')
+
 _cros_core_cmd_arg = CmdArg(
     '--cros_core', action='store_true',
     help='Finalize for ChromeOS Core devices (may add or remove few test '
@@ -502,13 +506,12 @@
   Cr50WriteFlashInfo(options)
 
 
-@Command('cr50_write_flash_info',
-         _rma_mode_cmd_arg,
-         _enable_zero_touch_cmd_arg)
+@Command('cr50_write_flash_info', _rma_mode_cmd_arg,
+         _replacement_mlb_mode_cmd_arg, _enable_zero_touch_cmd_arg)
 def Cr50WriteFlashInfo(options):
   """Set the serial number bits, board id and flags on the Cr50 chip."""
   GetGooftool(options).Cr50WriteFlashInfo(
-      options.enable_zero_touch, options.rma_mode)
+      options.enable_zero_touch, options.rma_mode, options.replacement_mlb_mode)
   event_log.Log('cr50_write_flash_info')
 
 
@@ -524,20 +527,21 @@
   return GetGooftool(options).Cr50DisableFactoryMode()
 
 
-@Command('cr50_finalize',
-         _no_write_protect_cmd_arg,
-         _rma_mode_cmd_arg,
-         _enable_zero_touch_cmd_arg)
+@Command('cr50_finalize', _no_write_protect_cmd_arg, _rma_mode_cmd_arg,
+         _replacement_mlb_mode_cmd_arg, _enable_zero_touch_cmd_arg)
 def Cr50Finalize(options):
   """Finalize steps for cr50."""
   if options.no_write_protect:
     logging.warning('SWWP is not enabled. Skip setting RO hash.')
   elif options.rma_mode:
     logging.warning('RMA mode. Skip setting RO hash.')
+  elif options.replacement_mlb_mode:
+    logging.warning('Replacement MLB mode. Skip setting RO hash.')
   else:
     Cr50SetROHash(options)
   Cr50WriteFlashInfo(options)
-  Cr50DisableFactoryMode(options)
+  if not options.replacement_mlb_mode:
+    Cr50DisableFactoryMode(options)
 
 
 @Command('enable_release_partition',
@@ -853,6 +857,7 @@
     _hwid_vpd_data_file_cmd_arg,
     _no_write_protect_cmd_arg,
     _rma_mode_cmd_arg,
+    _replacement_mlb_mode_cmd_arg,
     _cros_core_cmd_arg,
     _has_ec_pubkey_cmd_arg,
     _ec_pubkey_path_cmd_arg,
@@ -885,6 +890,14 @@
   - Uploads system logs & reports
   - Wipes the testing kernel, rootfs, and stateful partition
   """
+  if options.replacement_mlb_mode:
+    # Replacement MLB mode only do cr50 finalize.
+    Cr50Finalize(options)
+    LogSourceHashes(options)
+    LogSystemDetails(options)
+    UploadReport(options)
+    return
+
   if not options.rma_mode:
     # Write VPD values related to RLZ ping into VPD.
     GetGooftool(options).WriteVPDForRLZPing(options.embargo_offset)
diff --git a/py/gooftool/core.py b/py/gooftool/core.py
index eef96ec..329472f 100644
--- a/py/gooftool/core.py
+++ b/py/gooftool/core.py
@@ -1040,9 +1040,10 @@
         logging.error('Board ID has already been set on Cr50!')
       elif result.status == 3:
         error_msg = 'Board ID and/or flag has been set DIFFERENTLY on Cr50!'
-        if arg_phase == 'pvt':
+        if arg_phase == 'dev':
+          logging.error(error_msg)
+        else:
           raise Error(error_msg)
-        logging.error(error_msg)
       else:  # General errors.
         raise Error('Failed to set board ID and flag on Cr50. '
                     '(args=%s)' % arg_phase)
@@ -1050,7 +1051,8 @@
       logging.exception('Failed to set Cr50 Board ID.')
       raise
 
-  def Cr50WriteFlashInfo(self, enable_zero_touch=False, rma_mode=False):
+  def Cr50WriteFlashInfo(self, enable_zero_touch=False, rma_mode=False,
+                         replacement_mlb_mode=False):
     """Write device info into cr50 flash."""
     cros_config = cros_config_module.CrosConfig(self._util.shell)
     is_whitelabel, whitelabel_tag = cros_config.GetWhiteLabelTag()
@@ -1073,9 +1075,12 @@
         raise Error('whitelabel_tag reported by cros_config and VPD does not '
                     'match.  Have you reboot the device after updating VPD '
                     'fields?')
-    if not rma_mode and enable_zero_touch:
+    if not rma_mode and not replacement_mlb_mode and enable_zero_touch:
       self.Cr50SetSnBits()
-    self.Cr50SetBoardId(is_whitelabel)
+    if is_whitelabel and replacement_mlb_mode:
+      self.Cr50WriteWhitelabelFlags()
+    else:
+      self.Cr50SetBoardId(is_whitelabel)
 
   def Cr50WriteWhitelabelFlags(self):
     cros_config = cros_config_module.CrosConfig(self._util.shell)
diff --git a/py/test/pytests/finalize.py b/py/test/pytests/finalize.py
index 06abaaa..4aec718 100644
--- a/py/test/pytests/finalize.py
+++ b/py/test/pytests/finalize.py
@@ -161,6 +161,8 @@
       Arg('rma_mode', bool,
           'Enable rma_mode, do not check for deprecated components.',
           default=False),
+      Arg('replacement_mlb_mode', bool,
+          'Enable replacement MLB mode, only do cr50 finalize.', default=False),
       Arg('is_cros_core', bool,
           'For ChromeOS Core device, skip setting firmware bitmap locale.',
           default=False),
@@ -360,6 +362,9 @@
     if self.args.rma_mode:
       command += ' --rma_mode'
       logging.info('Using RMA mode. Accept deprecated components')
+    if self.args.replacement_mlb_mode:
+      command += ' --replacement_mlb_mode'
+      logging.info('Using replacement MLB mode. Only do cr50 finalize')
     if self.args.is_cros_core:
       command += ' --cros_core'
       logging.info('ChromeOS Core device. Skip some check.')
diff --git a/py/test/test_lists/generic_grt.test_list.json b/py/test/test_lists/generic_grt.test_list.json
index a14c8f9..9a0d9fb 100644
--- a/py/test/test_lists/generic_grt.test_list.json
+++ b/py/test/test_lists/generic_grt.test_list.json
@@ -10,6 +10,8 @@
       "__comment_cbi_eeprom_wp_status": "Set this to 'Absent' for board without CBI EEPROM. Set this to 'Unlocked' for MLB layouts which requires CBI WP being disabled in pre-PVT and enabled in PVT.",
       "ec_pubkey_path": null,
       "ec_pubkey_hash": null,
+      "rma_mode": false,
+      "replacement_mlb_mode": false,
       "enable_zero_touch": false,
       "gooftool_skip_list": [],
       "gooftool_waive_list": [],
@@ -54,7 +56,8 @@
         "gooftool_waive_list": "eval! constants.grt.gooftool_waive_list",
         "hwid_need_vpd": "eval! constants.hwid_need_vpd",
         "has_ec_pubkey": "eval! constants.has_ec_pubkey",
-        "rma_mode": "eval! constants.rma_mode",
+        "rma_mode": "eval! constants.grt.rma_mode",
+        "replacement_mlb_mode": "eval! constants.grt.replacement_mlb_mode",
         "secure_wipe": "eval! constants.grt.secure_wipe",
         "upload_method": "eval! 'shopfloor' if constants.enable_factory_server else 'none'",
         "write_protection": "eval! constants.grt.force_write_protect or options.phase == 'PVT'",
diff --git a/py/test/test_lists/generic_replacement_mlb.test_list.json b/py/test/test_lists/generic_replacement_mlb.test_list.json
new file mode 100644
index 0000000..72b7447
--- /dev/null
+++ b/py/test/test_lists/generic_replacement_mlb.test_list.json
@@ -0,0 +1,50 @@
+{
+  "inherit": [
+    "disable_factory_server.test_list",
+    "generic_grt.test_list"
+  ],
+  "label": "Generic Replacement MLB SMT",
+  "constants": {
+    "grt": {
+      "replacement_mlb_mode": true
+    },
+    "phase": "PVT",
+    "replacement_mlb_factory_server": "none"
+  },
+  "definitions": {
+    "ReplacementMLBEnd": {
+      "inherit": "TestGroup",
+      "label": "Replacement MLB End",
+      "__comment": "Wrap-up steps before shipping, e.g. shutdown",
+      "subtests": [
+        "Placeholder"
+      ]
+    },
+    "ReplacementMLBGRT": {
+      "inherit": "TestGroup",
+      "label": "Replacement MLB GRT (Google Required Tests)",
+      "subtests": [
+        "RebootStep",
+        "ReplacementMLBGRTFinalize"
+      ]
+    },
+    "ReplacementMLBGRTFinalize": {
+      "inherit": "GRTFinalize",
+      "args": {
+        "upload_method": "eval! constants.replacement_mlb_factory_server"
+      }
+    },
+    "ReplacementMLBSMT": {
+      "inherit": "TestGroup",
+      "label": "Replacement MLB SMT",
+      "subtests": [
+        "Placeholder"
+      ]
+    }
+  },
+  "tests": [
+    "ReplacementMLBSMT",
+    "ReplacementMLBGRT",
+    "ReplacementMLBEnd"
+  ]
+}
diff --git a/py/test/test_lists/generic_rma.test_list.json b/py/test/test_lists/generic_rma.test_list.json
index e8e3c96..ba70d94 100644
--- a/py/test/test_lists/generic_rma.test_list.json
+++ b/py/test/test_lists/generic_rma.test_list.json
@@ -5,9 +5,11 @@
   ],
   "label": "Generic RMA",
   "constants": {
+    "grt": {
+      "rma_mode": true
+    },
     "phase": "PVT",
-    "rma_factory_server": "none",
-    "rma_mode": true
+    "rma_factory_server": "none"
   },
   "definitions": {
     "RMAFFT": {