#include "intel.h"
 #include "nfit.h"
 
-static enum nvdimm_security_state intel_security_state(struct nvdimm *nvdimm,
+static unsigned long intel_security_flags(struct nvdimm *nvdimm,
                enum nvdimm_passphrase_type ptype)
 {
        struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
+       unsigned long security_flags = 0;
        struct {
                struct nd_cmd_pkg pkg;
                struct nd_intel_get_security_state cmd;
        int rc;
 
        if (!test_bit(NVDIMM_INTEL_GET_SECURITY_STATE, &nfit_mem->dsm_mask))
-               return -ENXIO;
+               return 0;
 
        /*
         * Short circuit the state retrieval while we are doing overwrite.
         * until the overwrite DSM completes.
         */
        if (nvdimm_in_overwrite(nvdimm) && ptype == NVDIMM_USER)
-               return NVDIMM_SECURITY_OVERWRITE;
+               return BIT(NVDIMM_SECURITY_OVERWRITE);
 
        rc = nvdimm_ctl(nvdimm, ND_CMD_CALL, &nd_cmd, sizeof(nd_cmd), NULL);
-       if (rc < 0)
-               return rc;
-       if (nd_cmd.cmd.status)
-               return -EIO;
+       if (rc < 0 || nd_cmd.cmd.status) {
+               pr_err("%s: security state retrieval failed (%d:%#x)\n",
+                               nvdimm_name(nvdimm), rc, nd_cmd.cmd.status);
+               return 0;
+       }
 
        /* check and see if security is enabled and locked */
        if (ptype == NVDIMM_MASTER) {
                if (nd_cmd.cmd.extended_state & ND_INTEL_SEC_ESTATE_ENABLED)
-                       return NVDIMM_SECURITY_UNLOCKED;
-               else if (nd_cmd.cmd.extended_state &
-                               ND_INTEL_SEC_ESTATE_PLIMIT)
-                       return NVDIMM_SECURITY_FROZEN;
-       } else {
-               if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_UNSUPPORTED)
-                       return -ENXIO;
-               else if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_ENABLED) {
-                       if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_LOCKED)
-                               return NVDIMM_SECURITY_LOCKED;
-                       else if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_FROZEN
-                                       || nd_cmd.cmd.state &
-                                       ND_INTEL_SEC_STATE_PLIMIT)
-                               return NVDIMM_SECURITY_FROZEN;
-                       else
-                               return NVDIMM_SECURITY_UNLOCKED;
-               }
+                       set_bit(NVDIMM_SECURITY_UNLOCKED, &security_flags);
+               else
+                       set_bit(NVDIMM_SECURITY_DISABLED, &security_flags);
+               if (nd_cmd.cmd.extended_state & ND_INTEL_SEC_ESTATE_PLIMIT)
+                       set_bit(NVDIMM_SECURITY_FROZEN, &security_flags);
+               return security_flags;
        }
 
-       /* this should cover master security disabled as well */
-       return NVDIMM_SECURITY_DISABLED;
+       if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_UNSUPPORTED)
+               return 0;
+
+       if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_ENABLED) {
+               if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_FROZEN ||
+                   nd_cmd.cmd.state & ND_INTEL_SEC_STATE_PLIMIT)
+                       set_bit(NVDIMM_SECURITY_FROZEN, &security_flags);
+
+               if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_LOCKED)
+                       set_bit(NVDIMM_SECURITY_LOCKED, &security_flags);
+               else
+                       set_bit(NVDIMM_SECURITY_UNLOCKED, &security_flags);
+       } else
+               set_bit(NVDIMM_SECURITY_DISABLED, &security_flags);
+
+       return security_flags;
 }
 
 static int intel_security_freeze(struct nvdimm *nvdimm)
 #endif
 
 static const struct nvdimm_security_ops __intel_security_ops = {
-       .state = intel_security_state,
+       .get_flags = intel_security_flags,
        .freeze = intel_security_freeze,
        .change_key = intel_security_change_key,
        .disable = intel_security_disable,
 
 
                /* We are shutting down. Make state frozen artificially. */
                nvdimm_bus_lock(dev);
-               nvdimm->sec.state = NVDIMM_SECURITY_FROZEN;
+               set_bit(NVDIMM_SECURITY_FROZEN, &nvdimm->sec.flags);
                if (test_and_clear_bit(NDD_WORK_PENDING, &nvdimm->flags))
                        dev_put = true;
                nvdimm_bus_unlock(dev);
 
 {
        struct nvdimm *nvdimm = to_nvdimm(dev);
 
-       switch (nvdimm->sec.state) {
-       case NVDIMM_SECURITY_DISABLED:
+       if (test_bit(NVDIMM_SECURITY_DISABLED, &nvdimm->sec.flags))
                return sprintf(buf, "disabled\n");
-       case NVDIMM_SECURITY_UNLOCKED:
+       if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags))
                return sprintf(buf, "unlocked\n");
-       case NVDIMM_SECURITY_LOCKED:
+       if (test_bit(NVDIMM_SECURITY_LOCKED, &nvdimm->sec.flags))
                return sprintf(buf, "locked\n");
-       case NVDIMM_SECURITY_FROZEN:
-               return sprintf(buf, "frozen\n");
-       case NVDIMM_SECURITY_OVERWRITE:
+       if (test_bit(NVDIMM_SECURITY_OVERWRITE, &nvdimm->sec.flags))
                return sprintf(buf, "overwrite\n");
-       default:
-               return -ENOTTY;
-       }
-
        return -ENOTTY;
 }
 
+static ssize_t frozen_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct nvdimm *nvdimm = to_nvdimm(dev);
+
+       return sprintf(buf, "%d\n", test_bit(NVDIMM_SECURITY_FROZEN,
+                               &nvdimm->sec.flags));
+}
+static DEVICE_ATTR_RO(frozen);
+
 #define OPS                                                    \
        C( OP_FREEZE,           "freeze",               1),     \
        C( OP_DISABLE,          "disable",              2),     \
        &dev_attr_commands.attr,
        &dev_attr_available_slots.attr,
        &dev_attr_security.attr,
+       &dev_attr_frozen.attr,
        NULL,
 };
 
        struct device *dev = container_of(kobj, typeof(*dev), kobj);
        struct nvdimm *nvdimm = to_nvdimm(dev);
 
-       if (a != &dev_attr_security.attr)
+       if (a != &dev_attr_security.attr && a != &dev_attr_frozen.attr)
                return a->mode;
-       if (nvdimm->sec.state < 0)
+       if (!nvdimm->sec.flags)
                return 0;
-       /* Are there any state mutation ops? */
-       if (nvdimm->sec.ops->freeze || nvdimm->sec.ops->disable
-                       || nvdimm->sec.ops->change_key
-                       || nvdimm->sec.ops->erase
-                       || nvdimm->sec.ops->overwrite)
+
+       if (a == &dev_attr_security.attr) {
+               /* Are there any state mutation ops (make writable)? */
+               if (nvdimm->sec.ops->freeze || nvdimm->sec.ops->disable
+                               || nvdimm->sec.ops->change_key
+                               || nvdimm->sec.ops->erase
+                               || nvdimm->sec.ops->overwrite)
+                       return a->mode;
+               return 0444;
+       }
+
+       if (nvdimm->sec.ops->freeze)
                return a->mode;
-       return 0444;
+       return 0;
 }
 
 struct attribute_group nvdimm_attribute_group = {
         * attribute visibility.
         */
        /* get security state and extended (master) state */
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
-       nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, NVDIMM_MASTER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
+       nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER);
        nd_device_register(dev);
 
        return nvdimm;
 {
        struct nvdimm *nvdimm = to_nvdimm(dev);
 
-       if (nvdimm->sec.state < 0 || !nvdimm->sec.ops
+       if (!nvdimm->sec.flags || !nvdimm->sec.ops
                        || !nvdimm->sec.ops->overwrite)
                return 0;
        nvdimm->sec.overwrite_state = sysfs_get_dirent(dev->kobj.sd, "security");
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->freeze)
                return -EOPNOTSUPP;
 
-       if (nvdimm->sec.state < 0)
+       if (!nvdimm->sec.flags)
                return -EIO;
 
        if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
        }
 
        rc = nvdimm->sec.ops->freeze(nvdimm);
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
 
        return rc;
 }
 
        const char *dimm_id;
        struct {
                const struct nvdimm_security_ops *ops;
-               enum nvdimm_security_state state;
-               enum nvdimm_security_state ext_state;
+               unsigned long flags;
+               unsigned long ext_flags;
                unsigned int overwrite_tmo;
                struct kernfs_node *overwrite_state;
        } sec;
        struct delayed_work dwork;
 };
 
-static inline enum nvdimm_security_state nvdimm_security_state(
+static inline unsigned long nvdimm_security_flags(
                struct nvdimm *nvdimm, enum nvdimm_passphrase_type ptype)
 {
+       u64 flags;
+       const u64 state_flags = 1UL << NVDIMM_SECURITY_DISABLED
+               | 1UL << NVDIMM_SECURITY_LOCKED
+               | 1UL << NVDIMM_SECURITY_UNLOCKED
+               | 1UL << NVDIMM_SECURITY_OVERWRITE;
+
        if (!nvdimm->sec.ops)
-               return -ENXIO;
+               return 0;
 
-       return nvdimm->sec.ops->state(nvdimm, ptype);
+       flags = nvdimm->sec.ops->get_flags(nvdimm, ptype);
+       /* disabled, locked, unlocked, and overwrite are mutually exclusive */
+       dev_WARN_ONCE(&nvdimm->dev, hweight64(flags & state_flags) > 1,
+                       "reported invalid security state: %#llx\n",
+                       (unsigned long long) flags);
+       return flags;
 }
 int nvdimm_security_freeze(struct nvdimm *nvdimm);
 #if IS_ENABLED(CONFIG_NVDIMM_KEYS)
 
        }
 
        nvdimm_put_key(key);
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
        return 0;
 }
 
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
 
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->unlock
-                       || nvdimm->sec.state < 0)
+                       || !nvdimm->sec.flags)
                return -EIO;
 
        if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
         * freeze of the security configuration. I.e. if the OS does not
         * have the key, security is being managed pre-OS.
         */
-       if (nvdimm->sec.state == NVDIMM_SECURITY_UNLOCKED) {
+       if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags)) {
                if (!key_revalidate)
                        return 0;
 
                        rc == 0 ? "success" : "fail");
 
        nvdimm_put_key(key);
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
        return rc;
 }
 
        return rc;
 }
 
+static int check_security_state(struct nvdimm *nvdimm)
+{
+       struct device *dev = &nvdimm->dev;
+
+       if (test_bit(NVDIMM_SECURITY_FROZEN, &nvdimm->sec.flags)) {
+               dev_dbg(dev, "Incorrect security state: %#lx\n",
+                               nvdimm->sec.flags);
+               return -EIO;
+       }
+
+       if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+               dev_dbg(dev, "Security operation in progress.\n");
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
 int nvdimm_security_disable(struct nvdimm *nvdimm, unsigned int keyid)
 {
        struct device *dev = &nvdimm->dev;
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
 
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->disable
-                       || nvdimm->sec.state < 0)
+                       || !nvdimm->sec.flags)
                return -EOPNOTSUPP;
 
-       if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
-               dev_dbg(dev, "Incorrect security state: %d\n",
-                               nvdimm->sec.state);
-               return -EIO;
-       }
-
-       if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
-               dev_dbg(dev, "Security operation in progress.\n");
-               return -EBUSY;
-       }
+       rc = check_security_state(nvdimm);
+       if (rc)
+               return rc;
 
        data = nvdimm_get_user_key_payload(nvdimm, keyid,
                        NVDIMM_BASE_KEY, &key);
                        rc == 0 ? "success" : "fail");
 
        nvdimm_put_key(key);
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
        return rc;
 }
 
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
 
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->change_key
-                       || nvdimm->sec.state < 0)
+                       || !nvdimm->sec.flags)
                return -EOPNOTSUPP;
 
-       if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
-               dev_dbg(dev, "Incorrect security state: %d\n",
-                               nvdimm->sec.state);
-               return -EIO;
-       }
+       rc = check_security_state(nvdimm);
+       if (rc)
+               return rc;
 
        data = nvdimm_get_user_key_payload(nvdimm, keyid,
                        NVDIMM_BASE_KEY, &key);
        nvdimm_put_key(newkey);
        nvdimm_put_key(key);
        if (pass_type == NVDIMM_MASTER)
-               nvdimm->sec.ext_state = nvdimm_security_state(nvdimm,
+               nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm,
                                NVDIMM_MASTER);
        else
-               nvdimm->sec.state = nvdimm_security_state(nvdimm,
+               nvdimm->sec.flags = nvdimm_security_flags(nvdimm,
                                NVDIMM_USER);
        return rc;
 }
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
 
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->erase
-                       || nvdimm->sec.state < 0)
+                       || !nvdimm->sec.flags)
                return -EOPNOTSUPP;
 
        if (atomic_read(&nvdimm->busy)) {
                return -EBUSY;
        }
 
-       if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
-               dev_dbg(dev, "Incorrect security state: %d\n",
-                               nvdimm->sec.state);
-               return -EIO;
-       }
-
-       if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
-               dev_dbg(dev, "Security operation in progress.\n");
-               return -EBUSY;
-       }
+       rc = check_security_state(nvdimm);
+       if (rc)
+               return rc;
 
-       if (nvdimm->sec.ext_state != NVDIMM_SECURITY_UNLOCKED
+       if (!test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.ext_flags)
                        && pass_type == NVDIMM_MASTER) {
                dev_dbg(dev,
                        "Attempt to secure erase in wrong master state.\n");
                        rc == 0 ? "success" : "fail");
 
        nvdimm_put_key(key);
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
        return rc;
 }
 
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
 
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->overwrite
-                       || nvdimm->sec.state < 0)
+                       || !nvdimm->sec.flags)
                return -EOPNOTSUPP;
 
        if (atomic_read(&nvdimm->busy)) {
                return -EINVAL;
        }
 
-       if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
-               dev_dbg(dev, "Incorrect security state: %d\n",
-                               nvdimm->sec.state);
-               return -EIO;
-       }
-
-       if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
-               dev_dbg(dev, "Security operation in progress.\n");
-               return -EBUSY;
-       }
+       rc = check_security_state(nvdimm);
+       if (rc)
+               return rc;
 
        data = nvdimm_get_user_key_payload(nvdimm, keyid,
                        NVDIMM_BASE_KEY, &key);
        if (rc == 0) {
                set_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
                set_bit(NDD_WORK_PENDING, &nvdimm->flags);
-               nvdimm->sec.state = NVDIMM_SECURITY_OVERWRITE;
+               set_bit(NVDIMM_SECURITY_OVERWRITE, &nvdimm->sec.flags);
                /*
                 * Make sure we don't lose device while doing overwrite
                 * query.
        tmo = nvdimm->sec.overwrite_tmo;
 
        if (!nvdimm->sec.ops || !nvdimm->sec.ops->query_overwrite
-                       || nvdimm->sec.state < 0)
+                       || !nvdimm->sec.flags)
                return;
 
        rc = nvdimm->sec.ops->query_overwrite(nvdimm);
        clear_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
        clear_bit(NDD_WORK_PENDING, &nvdimm->flags);
        put_device(&nvdimm->dev);
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
-       nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, NVDIMM_MASTER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER);
 }
 
 void nvdimm_security_overwrite_query(struct work_struct *work)
 
 
 }
 
-enum nvdimm_security_state {
-       NVDIMM_SECURITY_ERROR = -1,
+/*
+ * Note that separate bits for locked + unlocked are defined so that
+ * 'flags == 0' corresponds to an error / not-supported state.
+ */
+enum nvdimm_security_bits {
        NVDIMM_SECURITY_DISABLED,
        NVDIMM_SECURITY_UNLOCKED,
        NVDIMM_SECURITY_LOCKED,
 };
 
 struct nvdimm_security_ops {
-       enum nvdimm_security_state (*state)(struct nvdimm *nvdimm,
+       unsigned long (*get_flags)(struct nvdimm *nvdimm,
                        enum nvdimm_passphrase_type pass_type);
        int (*freeze)(struct nvdimm *nvdimm);
        int (*change_key)(struct nvdimm *nvdimm,
 
         * For the test version we need to poll the "hardware" in order
         * to get the updated status for unlock testing.
         */
-       nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
-       nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, NVDIMM_MASTER);
+       nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
 
-       switch (nvdimm->sec.state) {
-       case NVDIMM_SECURITY_DISABLED:
+       if (test_bit(NVDIMM_SECURITY_DISABLED, &nvdimm->sec.flags))
                return sprintf(buf, "disabled\n");
-       case NVDIMM_SECURITY_UNLOCKED:
+       if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags))
                return sprintf(buf, "unlocked\n");
-       case NVDIMM_SECURITY_LOCKED:
+       if (test_bit(NVDIMM_SECURITY_LOCKED, &nvdimm->sec.flags))
                return sprintf(buf, "locked\n");
-       case NVDIMM_SECURITY_FROZEN:
-               return sprintf(buf, "frozen\n");
-       case NVDIMM_SECURITY_OVERWRITE:
-               return sprintf(buf, "overwrite\n");
-       default:
-               return -ENOTTY;
-       }
-
        return -ENOTTY;
 }
-