+/* Gets the fault status of 'cfm'. Returns true when 'cfm' has detected
+ * connectivity problems, false otherwise. */
+bool
+cfm_get_fault(const struct cfm *cfm)
+{
+ return cfm->fault;
+}
+
+/* Gets the operational state of 'cfm'. 'cfm' is considered operationally down
+ * if it has received a CCM with the operationally down bit set from any of its
+ * remote maintenance points. Returns true if 'cfm' is operationally up. False
+ * otherwise. */
+bool
+cfm_get_opup(const struct cfm *cfm)
+{
+ return cfm->remote_opup;
+}
+
+/* Populates 'rmps' with an array of remote maintenance points reachable by
+ * 'cfm'. The number of remote maintenance points is written to 'n_rmps'.
+ * 'cfm' retains ownership of the array written to 'rmps' */
+void
+cfm_get_remote_mpids(const struct cfm *cfm, const uint64_t **rmps,
+ size_t *n_rmps)
+{
+ *rmps = cfm->rmps_array;
+ *n_rmps = cfm->rmps_array_len;
+}
+
+static struct cfm *
+cfm_find(const char *name)
+{
+ struct cfm *cfm;
+
+ HMAP_FOR_EACH_WITH_HASH (cfm, hmap_node, hash_string(name, 0), &all_cfms) {
+ if (!strcmp(cfm->name, name)) {
+ return cfm;
+ }
+ }
+ return NULL;
+}
+
+static void
+cfm_print_details(struct ds *ds, const struct cfm *cfm)
+{
+ struct remote_mp *rmp;
+
+ ds_put_format(ds, "---- %s ----\n", cfm->name);
+ ds_put_format(ds, "MPID %"PRIu64":%s%s%s\n", cfm->mpid,
+ cfm->extended ? " extended" : "",
+ cfm->fault ? " fault" : "",
+ cfm->unexpected_recv ? " unexpected_recv" : "");
+
+ ds_put_format(ds, "\topstate: %s\n", cfm->opup ? "up" : "down");
+ ds_put_format(ds, "\tremote_opstate: %s\n",
+ cfm->remote_opup ? "up" : "down");
+ ds_put_format(ds, "\tinterval: %dms\n", cfm->ccm_interval_ms);
+ ds_put_format(ds, "\tnext CCM tx: %lldms\n",
+ timer_msecs_until_expired(&cfm->tx_timer));
+ ds_put_format(ds, "\tnext fault check: %lldms\n",
+ timer_msecs_until_expired(&cfm->fault_timer));
+
+ HMAP_FOR_EACH (rmp, node, &cfm->remote_mps) {
+ ds_put_format(ds, "Remote MPID %"PRIu64":%s\n",
+ rmp->mpid,
+ rmp->rdi ? " rdi" : "");
+ ds_put_format(ds, "\trecv since check: %s\n",
+ rmp->recv ? "true" : "false");
+ ds_put_format(ds, "\topstate: %s\n", rmp->opup? "up" : "down");
+ }
+}
+
+static void
+cfm_unixctl_show(struct unixctl_conn *conn,
+ const char *args, void *aux OVS_UNUSED)
+{
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ const struct cfm *cfm;
+
+ if (strlen(args)) {
+ cfm = cfm_find(args);
+ if (!cfm) {
+ unixctl_command_reply(conn, 501, "no such CFM object");
+ return;
+ }
+ cfm_print_details(&ds, cfm);
+ } else {
+ HMAP_FOR_EACH (cfm, hmap_node, &all_cfms) {
+ cfm_print_details(&ds, cfm);