Fixes for PMF and CCP drivers after S4
Subsystems (4)
v6 cover Fixes for PMF and CCP drivers after S4
Lars Francke reported that the PMF driver fails to work afer S4 with:
ccp 0000:c3:00.2: tee: command 0x5 timed out, disabling PSP
This is because there is a TA loaded to the TEE environment that is lost during S4. The TEE rings need to be reinitialized and the TA needs to be reloaded.
This series adds those flows to the PMF and CCP drivers.
v5->v6:
* Fix Tom's feedback on patch 3/5
Mario Limonciello (AMD) (4):
crypto: ccp - Declare PSP dead if PSP_CMD_TEE_RING_INIT fails
crypto: ccp - Add an S4 restore flow
crypto: ccp - Factor out ring destroy handling to a helper
crypto: ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT
fails
Shyam Sundar S K (1):
platform/x86/amd/pmf: Prevent TEE errors after hibernate
drivers/crypto/ccp/psp-dev.c | 11 +++++ drivers/crypto/ccp/sp-dev.c | 12 ++++++ drivers/crypto/ccp/sp-dev.h | 3 ++ drivers/crypto/ccp/sp-pci.c | 16 ++++++- drivers/crypto/ccp/tee-dev.c | 56 ++++++++++++++++++------ drivers/crypto/ccp/tee-dev.h | 1 + drivers/platform/x86/amd/pmf/core.c | 62 ++++++++++++++++++++++++++- drivers/platform/x86/amd/pmf/pmf.h | 10 +++++ drivers/platform/x86/amd/pmf/tee-if.c | 12 ++---- include/linux/psp.h | 1 + 10 files changed, 161 insertions(+), 23 deletions(-)
Signature · 1 line
2.43.0
PATCH v6 1/5 platform/x86/amd/pmf: Prevent TEE errors after hibernate
From: Shyam Sundar S K <[email protected]>
After resuming from hibernate, TEE commands can time out and cause PSP disables. Fix this by reinitializing the Trusted Application (TA) and cancelling the pb workqueue in the hibernate callbacks to avoid these errors.
ccp 0000:c4:00.2: tee: command 0x5 timed out, disabling PSP amd-pmf AMDI0107:00: TEE enact cmd failed. err: ffff000e, ret:0 amd-pmf AMDI0107:00: TEE enact cmd failed. err: ffff000e, ret:0 amd-pmf AMDI0107:00: TEE enact cmd failed. err: ffff000e, ret:0
--- drivers/platform/x86/amd/pmf/core.c | 62 ++++++++++++++++++++++++++- drivers/platform/x86/amd/pmf/pmf.h | 10 +++++ drivers/platform/x86/amd/pmf/tee-if.c | 12 ++---- 3 files changed, 74 insertions(+), 10 deletions(-)
drivers/platform/x86/amd/pmf/core.c +61 -1
@@ -314,6 +314,61 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)return 0 ; } +static int amd_pmf_reinit_ta (struct amd_pmf_dev *pdev) +{ +bool status; +int ret, i; + +for (i =0 ; i <ARRAY_SIZE (amd_pmf_ta_uuid); i++) { + ret =amd_pmf_tee_init (pdev, &amd_pmf_ta_uuid[i]); +if (ret) { +dev_err (pdev->dev,"TEE init failed for UUID[%d] ret: %d\n" , i, ret); +return ret; + } + + ret =amd_pmf_start_policy_engine (pdev); +dev_dbg (pdev->dev,"start policy engine ret: %d (UUID idx: %d)\n" , ret, i); + status = ret ==TA_PMF_TYPE_SUCCESS ; +if (status) +break ; +amd_pmf_tee_deinit (pdev); + } + +return 0 ; +} + +static int amd_pmf_restore_handler (struct device *dev) +{ +struct amd_pmf_dev *pdev =dev_get_drvdata (dev); +int ret; + +if (pdev->buf) { + ret =amd_pmf_set_dram_addr (pdev, false); +if (ret) +return ret; + } + +if (pdev->smart_pc_enabled) +amd_pmf_reinit_ta (pdev); + +return 0 ; +} + +static int amd_pmf_freeze_handler (struct device *dev) +{ +struct amd_pmf_dev *pdev =dev_get_drvdata (dev); + +if (!pdev->smart_pc_enabled) +return 0 ; + +cancel_delayed_work_sync (&pdev->pb_work); +/* Clear all TEE resources */ +amd_pmf_tee_deinit (pdev); + pdev->session_id =0 ; + +return 0 ; +} +static int amd_pmf_suspend_handler (struct device *dev) {struct amd_pmf_dev *pdev =dev_get_drvdata (dev);
@@ -347,7 +402,12 @@ static int amd_pmf_resume_handler(struct device *dev)return 0 ; } -static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler); +static const struct dev_pm_ops amd_pmf_pm = { + .suspend = amd_pmf_suspend_handler, + .resume = amd_pmf_resume_handler, + .freeze = amd_pmf_freeze_handler, + .restore = amd_pmf_restore_handler, +};static void amd_pmf_init_features (struct amd_pmf_dev *dev) {
drivers/platform/x86/amd/pmf/pmf.h +10 -0
@@ -129,6 +129,12 @@ struct cookie_header {typedef void (*apmf_event_handler_t )(acpi_handle handle,u32 event,void *data); +static const uuid_t amd_pmf_ta_uuid[]__used = {UUID_INIT (0xd9b39bf2 ,0x66bd ,0x4154 ,0xaf ,0xb8 , +0x8a ,0xcc ,0x2b ,0x2b ,0x60 ,0xd6 ), +UUID_INIT (0x6fd93b77 ,0x3fb8 ,0x524d ,0xb1 ,0x2d , +0xc5 ,0x29 ,0xb1 ,0x3d ,0x85 ,0x43 ), + }; +/* APTS PMF BIOS Interface */ struct amd_pmf_apts_output {u16 table_version;
@@ -895,4 +901,8 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tabvoid amd_pmf_dump_ta_inputs (struct amd_pmf_dev *dev,struct ta_pmf_enact_table *in);int amd_pmf_invoke_cmd_enact (struct amd_pmf_dev *dev); +int amd_pmf_tee_init (struct amd_pmf_dev *dev,const uuid_t *uuid); +void amd_pmf_tee_deinit (struct amd_pmf_dev *dev); +int amd_pmf_start_policy_engine (struct amd_pmf_dev *dev); +#endif /* PMF_H */
drivers/platform/x86/amd/pmf/tee-if.c +3 -9
@@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444);MODULE_PARM_DESC (pb_side_load,"Sideload policy binaries debug policy failures" );#endif -static const uuid_t amd_pmf_ta_uuid[] = {UUID_INIT (0xd9b39bf2 ,0x66bd ,0x4154 ,0xaf ,0xb8 ,0x8a , -0xcc ,0x2b ,0x2b ,0x60 ,0xd6 ), -UUID_INIT (0x6fd93b77 ,0x3fb8 ,0x524d ,0xb1 ,0x2d ,0xc5 , -0x29 ,0xb1 ,0x3d ,0x85 ,0x43 ), - }; -static const char *amd_pmf_uevent_as_str (unsigned int state) {switch (state) {
@@ -324,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work)schedule_delayed_work (&dev->pb_work,msecs_to_jiffies (pb_actions_ms)); } -static int amd_pmf_start_policy_engine (struct amd_pmf_dev *dev) +int amd_pmf_start_policy_engine (struct amd_pmf_dev *dev) {struct cookie_header *header;int res;
@@ -480,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)return 0 ; } -static int amd_pmf_tee_init (struct amd_pmf_dev *dev,const uuid_t *uuid) +int amd_pmf_tee_init (struct amd_pmf_dev *dev,const uuid_t *uuid) {u32 size;int ret;
@@ -528,7 +522,7 @@ static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)return ret; } -static void amd_pmf_tee_deinit (struct amd_pmf_dev *dev) +void amd_pmf_tee_deinit (struct amd_pmf_dev *dev) {if (!dev->tee_ctx)return ;
Signature · 1 line
2.43.0
PATCH v6 2/5 crypto: ccp - Declare PSP dead if PSP_CMD_TEE_RING_INIT fails
In reply to Mario Limonciello (AMD)tee_init_ring() only declares PSP dead if the command times out. If there is any other failure it is still considered fatal though. Set psp_dead for other failures as well.
--- drivers/crypto/ccp/tee-dev.c | 1 + 1 file changed, 1 insertion(+)
drivers/crypto/ccp/tee-dev.c +1 -0
@@ -125,6 +125,7 @@ static int tee_init_ring(struct psp_tee_device *tee)dev_err (tee->dev,"tee: ring init command failed (%#010lx)\n" ,FIELD_GET (PSP_CMDRESP_STS , reg));tee_free_ring (tee); + psp_dead = true; ret = -EIO ; }
Signature · 1 line
2.43.0
Re: [PATCH v6 2/5] crypto: ccp - Declare PSP dead if PSP_CMD_TEE_RING_INIT fails
Quoted 7 lines · Mario Limonciello (AMD)
tee_init_ring()only declares PSP dead if the command times out. If there is any other failure it is still considered fatal though. Setpsp_deadfor other failures as well.Fixes 949a0c8dd3c2 ("crypto: ccp - Move direct access to some PSP registers out of TEE")Tested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Thanks,
Shyam
Quoted 16 lines
--- drivers/crypto/ccp/tee-dev.c | 1 + 1 file changed, 1 insertion(+)drivers/crypto/ccp/tee-dev.c +1
-0@@ -125,6 +125,7 @@ static int tee_init_ring(struct psp_tee_device *tee)dev_err (tee->dev,"tee: ring init command failed (%#010lx)\n" ,FIELD_GET (PSP_CMDRESP_STS , reg));tee_free_ring (tee); + psp_dead = true; ret = -EIO ; }
Re: [PATCH v6 2/5] crypto: ccp - Declare PSP dead if PSP_CMD_TEE_RING_INIT fails
In reply to Mario Limonciello (AMD)Quoted 7 lines · Mario Limonciello (AMD)
tee_init_ring()only declares PSP dead if the command times out. If there is any other failure it is still considered fatal though. Setpsp_deadfor other failures as well.Fixes 949a0c8dd3c2 ("crypto: ccp - Move direct access to some PSP registers out of TEE")Tested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Quoted 16 lines
--- drivers/crypto/ccp/tee-dev.c | 1 + 1 file changed, 1 insertion(+)drivers/crypto/ccp/tee-dev.c +1
-0@@ -125,6 +125,7 @@ static int tee_init_ring(struct psp_tee_device *tee)dev_err (tee->dev,"tee: ring init command failed (%#010lx)\n" ,FIELD_GET (PSP_CMDRESP_STS , reg));tee_free_ring (tee); + psp_dead = true; ret = -EIO ; }
PATCH v6 3/5 crypto: ccp - Add an S4 restore flow
In reply to Mario Limonciello (AMD)The system will have lost power during S4. The ring used for TEE communications needs to be initialized before use.
---
v6:
* Add sp_restore() flow (Tom)
* Drop CONFIG_CRYPTO_DEV_SP_PSP conditional behavior
v5:
* Fix return for psp_restore() (LKP robot, Shyam)
v4:
* Add tag (Yijun)
* Remove blank line (Ilpo)
* Adjust whitespace in psp_restore() (Ilpo)
--- drivers/crypto/ccp/psp-dev.c | 11 +++++++++++ drivers/crypto/ccp/sp-dev.c | 12 ++++++++++++ drivers/crypto/ccp/sp-dev.h | 3 +++ drivers/crypto/ccp/sp-pci.c | 16 +++++++++++++++- drivers/crypto/ccp/tee-dev.c | 5 +++++ drivers/crypto/ccp/tee-dev.h | 1 + 6 files changed, 47 insertions(+), 1 deletion(-)
drivers/crypto/ccp/psp-dev.c +11 -0
@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)return sp ? sp->psp_data :NULL ; } +int psp_restore (struct sp_device *sp) +{ +struct psp_device *psp = sp->psp_data; +int ret =0 ; + +if (psp->tee_data) + ret =tee_restore (psp); + +return ret; +} +void psp_pci_init (void ) { psp_master =psp_get_master_device ();
drivers/crypto/ccp/sp-dev.c +12 -0
@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)return 0 ; } +int sp_restore (struct sp_device *sp) +{ +if (sp->psp_data) { +int ret =psp_restore (sp); + +if (ret) +return ret; + } + +return sp_resume (sp); +} +struct sp_device *sp_get_psp_master_device (void ) {struct sp_device *i, *ret =NULL ;
drivers/crypto/ccp/sp-dev.h +3 -0
@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);int sp_suspend (struct sp_device *sp);int sp_resume (struct sp_device *sp); +int sp_restore (struct sp_device *sp);int sp_request_ccp_irq (struct sp_device *sp,irq_handler_t handler,const char *name,void *data);void sp_free_ccp_irq (struct sp_device *sp,void *data);
@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);void psp_pci_init (void );void psp_dev_destroy (struct sp_device *sp);void psp_pci_exit (void ); +int psp_restore (struct sp_device *sp);#else /* !CONFIG_CRYPTO_DEV_SP_PSP */
@@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }static inline void psp_pci_init (void ) { }static inline void psp_dev_destroy (struct sp_device *sp) { }static inline void psp_pci_exit (void ) { } +static inline int psp_restore (struct sp_device *sp) {return 0 ; }#endif /* CONFIG_CRYPTO_DEV_SP_PSP */
drivers/crypto/ccp/sp-pci.c +15 -1
@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)return sp_resume (sp); } +static int __maybe_unused sp_pci_restore (struct device *dev) +{ +struct sp_device *sp =dev_get_drvdata (dev); + +return sp_restore (sp); +} +#ifdef CONFIG_CRYPTO_DEV_SP_PSP static const struct sev_vdata sevv1 = { .cmdresp_reg = 0x10580, /* C2PMSG_32 */
@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = { };MODULE_DEVICE_TABLE (pci, sp_pci_table); -static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume); +static const struct dev_pm_ops sp_pci_pm_ops = { + .suspend =pm_sleep_ptr (sp_pci_suspend), + .resume =pm_sleep_ptr (sp_pci_resume), + .freeze =pm_sleep_ptr (sp_pci_suspend), + .thaw =pm_sleep_ptr (sp_pci_resume), + .poweroff =pm_sleep_ptr (sp_pci_suspend), + .restore_early =pm_sleep_ptr (sp_pci_restore), +};static struct pci_driver sp_pci_driver = { .name ="ccp" ,
drivers/crypto/ccp/tee-dev.c +5 -0
@@ -366,3 +366,8 @@ int psp_check_tee_status(void)return 0 ; }EXPORT_SYMBOL (psp_check_tee_status); + +int tee_restore (struct psp_device *psp) +{ +return tee_init_ring (psp->tee_data); +}
drivers/crypto/ccp/tee-dev.h +1 -0
@@ -111,5 +111,6 @@ struct tee_ring_cmd {int tee_dev_init (struct psp_device *psp);void tee_dev_destroy (struct psp_device *psp); +int tee_restore (struct psp_device *psp);#endif /* __TEE_DEV_H__ */
Signature · 1 line
2.43.0
Re: [PATCH v6 3/5] crypto: ccp - Add an S4 restore flow
Quoted 8 lines · Mario Limonciello (AMD)
The system will have lost power during S4. The ring used for TEE communications needs to be initialized before use.
Fixes f892a21f51162 ("crypto: ccp - use generic power management")Reported-by Lars Francke <[email protected]>Closes https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ open hereTested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Thanks,
Shyam
Quoted 150 lines
---
v6:
* Addsp_restore()flow (Tom)
* DropCONFIG_CRYPTO_DEV_SP_PSPconditional behavior
v5:
* Fix return forpsp_restore()(LKP robot, Shyam)
v4:
* Add tag (Yijun)
* Remove blank line (Ilpo)
* Adjust whitespace inpsp_restore()(Ilpo)--- drivers/crypto/ccp/psp-dev.c | 11 +++++++++++ drivers/crypto/ccp/sp-dev.c | 12 ++++++++++++ drivers/crypto/ccp/sp-dev.h | 3 +++ drivers/crypto/ccp/sp-pci.c | 16 +++++++++++++++- drivers/crypto/ccp/tee-dev.c | 5 +++++ drivers/crypto/ccp/tee-dev.h | 1 + 6 files changed, 47 insertions(+), 1 deletion(-)drivers/crypto/ccp/psp-dev.c +11
-0@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)return sp ? sp->psp_data :NULL ; } +int psp_restore (struct sp_device *sp) +{ +struct psp_device *psp = sp->psp_data; +int ret =0 ; + +if (psp->tee_data) + ret =tee_restore (psp); + +return ret; +} +void psp_pci_init (void ) { psp_master =psp_get_master_device ();drivers/crypto/ccp/sp-dev.c +12
-0@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)return 0 ; } +int sp_restore (struct sp_device *sp) +{ +if (sp->psp_data) { +int ret =psp_restore (sp); + +if (ret) +return ret; + } + +return sp_resume (sp); +} +struct sp_device *sp_get_psp_master_device (void ) {struct sp_device *i, *ret =NULL ;drivers/crypto/ccp/sp-dev.h +3
-0@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);int sp_suspend (struct sp_device *sp);int sp_resume (struct sp_device *sp); +int sp_restore (struct sp_device *sp);int sp_request_ccp_irq (struct sp_device *sp,irq_handler_t handler,const char *name,void *data);void sp_free_ccp_irq (struct sp_device *sp,void *data);@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);void psp_pci_init (void );void psp_dev_destroy (struct sp_device *sp);void psp_pci_exit (void ); +int psp_restore (struct sp_device *sp);#else /* !CONFIG_CRYPTO_DEV_SP_PSP */ @@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }static inline void psp_pci_init (void ) { }static inline void psp_dev_destroy (struct sp_device *sp) { }static inline void psp_pci_exit (void ) { } +static inline int psp_restore (struct sp_device *sp) {return 0 ; }#endif /* CONFIG_CRYPTO_DEV_SP_PSP */ drivers/crypto/ccp/sp-pci.c +15
-1@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)return sp_resume (sp); } +static int __maybe_unused sp_pci_restore (struct device *dev) +{ +struct sp_device *sp =dev_get_drvdata (dev); + +return sp_restore (sp); +} +#ifdef CONFIG_CRYPTO_DEV_SP_PSP static const struct sev_vdata sevv1 = { .cmdresp_reg = 0x10580, /* C2PMSG_32 */@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = { };MODULE_DEVICE_TABLE (pci, sp_pci_table); -static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume); +static const struct dev_pm_ops sp_pci_pm_ops = { + .suspend =pm_sleep_ptr (sp_pci_suspend), + .resume =pm_sleep_ptr (sp_pci_resume), + .freeze =pm_sleep_ptr (sp_pci_suspend), + .thaw =pm_sleep_ptr (sp_pci_resume), + .poweroff =pm_sleep_ptr (sp_pci_suspend), + .restore_early =pm_sleep_ptr (sp_pci_restore), +};static struct pci_driver sp_pci_driver = { .name ="ccp" ,drivers/crypto/ccp/tee-dev.c +5
-0@@ -366,3 +366,8 @@ int psp_check_tee_status(void)return 0 ; }EXPORT_SYMBOL (psp_check_tee_status); + +int tee_restore (struct psp_device *psp) +{ +return tee_init_ring (psp->tee_data); +}drivers/crypto/ccp/tee-dev.h +1
-0@@ -111,5 +111,6 @@ struct tee_ring_cmd {int tee_dev_init (struct psp_device *psp);void tee_dev_destroy (struct psp_device *psp); +int tee_restore (struct psp_device *psp);#endif /* __TEE_DEV_H__ */
Re: [PATCH v6 3/5] crypto: ccp - Add an S4 restore flow
In reply to Mario Limonciello (AMD)Quoted 8 lines · Mario Limonciello (AMD)
The system will have lost power during S4. The ring used for TEE communications needs to be initialized before use.
Fixes f892a21f51162 ("crypto: ccp - use generic power management")Reported-by Lars Francke <[email protected]>Closes https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ open hereTested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Quoted 150 lines
---
v6:
* Addsp_restore()flow (Tom)
* DropCONFIG_CRYPTO_DEV_SP_PSPconditional behavior
v5:
* Fix return forpsp_restore()(LKP robot, Shyam)
v4:
* Add tag (Yijun)
* Remove blank line (Ilpo)
* Adjust whitespace inpsp_restore()(Ilpo)--- drivers/crypto/ccp/psp-dev.c | 11 +++++++++++ drivers/crypto/ccp/sp-dev.c | 12 ++++++++++++ drivers/crypto/ccp/sp-dev.h | 3 +++ drivers/crypto/ccp/sp-pci.c | 16 +++++++++++++++- drivers/crypto/ccp/tee-dev.c | 5 +++++ drivers/crypto/ccp/tee-dev.h | 1 + 6 files changed, 47 insertions(+), 1 deletion(-)drivers/crypto/ccp/psp-dev.c +11
-0@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)return sp ? sp->psp_data :NULL ; } +int psp_restore (struct sp_device *sp) +{ +struct psp_device *psp = sp->psp_data; +int ret =0 ; + +if (psp->tee_data) + ret =tee_restore (psp); + +return ret; +} +void psp_pci_init (void ) { psp_master =psp_get_master_device ();drivers/crypto/ccp/sp-dev.c +12
-0@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)return 0 ; } +int sp_restore (struct sp_device *sp) +{ +if (sp->psp_data) { +int ret =psp_restore (sp); + +if (ret) +return ret; + } + +return sp_resume (sp); +} +struct sp_device *sp_get_psp_master_device (void ) {struct sp_device *i, *ret =NULL ;drivers/crypto/ccp/sp-dev.h +3
-0@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);int sp_suspend (struct sp_device *sp);int sp_resume (struct sp_device *sp); +int sp_restore (struct sp_device *sp);int sp_request_ccp_irq (struct sp_device *sp,irq_handler_t handler,const char *name,void *data);void sp_free_ccp_irq (struct sp_device *sp,void *data);@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);void psp_pci_init (void );void psp_dev_destroy (struct sp_device *sp);void psp_pci_exit (void ); +int psp_restore (struct sp_device *sp);#else /* !CONFIG_CRYPTO_DEV_SP_PSP */ @@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }static inline void psp_pci_init (void ) { }static inline void psp_dev_destroy (struct sp_device *sp) { }static inline void psp_pci_exit (void ) { } +static inline int psp_restore (struct sp_device *sp) {return 0 ; }#endif /* CONFIG_CRYPTO_DEV_SP_PSP */ drivers/crypto/ccp/sp-pci.c +15
-1@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)return sp_resume (sp); } +static int __maybe_unused sp_pci_restore (struct device *dev) +{ +struct sp_device *sp =dev_get_drvdata (dev); + +return sp_restore (sp); +} +#ifdef CONFIG_CRYPTO_DEV_SP_PSP static const struct sev_vdata sevv1 = { .cmdresp_reg = 0x10580, /* C2PMSG_32 */@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = { };MODULE_DEVICE_TABLE (pci, sp_pci_table); -static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume); +static const struct dev_pm_ops sp_pci_pm_ops = { + .suspend =pm_sleep_ptr (sp_pci_suspend), + .resume =pm_sleep_ptr (sp_pci_resume), + .freeze =pm_sleep_ptr (sp_pci_suspend), + .thaw =pm_sleep_ptr (sp_pci_resume), + .poweroff =pm_sleep_ptr (sp_pci_suspend), + .restore_early =pm_sleep_ptr (sp_pci_restore), +};static struct pci_driver sp_pci_driver = { .name ="ccp" ,drivers/crypto/ccp/tee-dev.c +5
-0@@ -366,3 +366,8 @@ int psp_check_tee_status(void)return 0 ; }EXPORT_SYMBOL (psp_check_tee_status); + +int tee_restore (struct psp_device *psp) +{ +return tee_init_ring (psp->tee_data); +}drivers/crypto/ccp/tee-dev.h +1
-0@@ -111,5 +111,6 @@ struct tee_ring_cmd {int tee_dev_init (struct psp_device *psp);void tee_dev_destroy (struct psp_device *psp); +int tee_restore (struct psp_device *psp);#endif /* __TEE_DEV_H__ */
PATCH v6 4/5 crypto: ccp - Factor out ring destroy handling to a helper
In reply to Mario Limonciello (AMD)The ring destroy command needs to be used in multiple places. Split out the code to a helper.
--- drivers/crypto/ccp/tee-dev.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-)
drivers/crypto/ccp/tee-dev.c +24 -12
@@ -86,6 +86,29 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)kfree (cmd); } +static bool tee_send_destroy_cmd (struct psp_tee_device *tee) +{ +unsigned int reg; +int ret; + + ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_DESTROY ,NULL , +TEE_DEFAULT_CMD_TIMEOUT , ®); +if (ret) { +dev_err (tee->dev,"tee: ring destroy command timed out, disabling TEE support\n" ); + psp_dead = true; +return false; + } + +if (FIELD_GET (PSP_CMDRESP_STS , reg)) { +dev_err (tee->dev,"tee: ring destroy command failed (%#010lx)\n" , +FIELD_GET (PSP_CMDRESP_STS , reg)); + psp_dead = true; +return false; + } + +return true; +} +static int tee_init_ring (struct psp_tee_device *tee) {int ring_size =MAX_RING_BUFFER_ENTRIES *sizeof (struct tee_ring_cmd );
@@ -137,24 +160,13 @@ static int tee_init_ring(struct psp_tee_device *tee)static void tee_destroy_ring (struct psp_tee_device *tee) { -unsigned int reg; -int ret; -if (!tee->rb_mgr.ring_start)return ;if (psp_dead) goto free_ring; - ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_DESTROY ,NULL , -TEE_DEFAULT_CMD_TIMEOUT , ®); -if (ret) { -dev_err (tee->dev,"tee: ring destroy command timed out, disabling TEE support\n" ); - psp_dead = true; - }else if (FIELD_GET (PSP_CMDRESP_STS , reg)) { -dev_err (tee->dev,"tee: ring destroy command failed (%#010lx)\n" , -FIELD_GET (PSP_CMDRESP_STS , reg)); - } +tee_send_destroy_cmd (tee); free_ring:tee_free_ring (tee);
Signature · 1 line
2.43.0
Re: [PATCH v6 4/5] crypto: ccp - Factor out ring destroy handling to a helper
Quoted 5 lines · Mario Limonciello (AMD)
The ring destroy command needs to be used in multiple places. Split out the code to a helper.
Tested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Thanks,
Shyam
Quoted 64 lines
--- drivers/crypto/ccp/tee-dev.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-)drivers/crypto/ccp/tee-dev.c +24
-12@@ -86,6 +86,29 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)kfree (cmd); } +static bool tee_send_destroy_cmd (struct psp_tee_device *tee) +{ +unsigned int reg; +int ret; + + ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_DESTROY ,NULL , +TEE_DEFAULT_CMD_TIMEOUT , ®); +if (ret) { +dev_err (tee->dev,"tee: ring destroy command timed out, disabling TEE support\n" ); + psp_dead = true; +return false; + } + +if (FIELD_GET (PSP_CMDRESP_STS , reg)) { +dev_err (tee->dev,"tee: ring destroy command failed (%#010lx)\n" , +FIELD_GET (PSP_CMDRESP_STS , reg)); + psp_dead = true; +return false; + } + +return true; +} +static int tee_init_ring (struct psp_tee_device *tee) {int ring_size =MAX_RING_BUFFER_ENTRIES *sizeof (struct tee_ring_cmd );@@ -137,24 +160,13 @@ static int tee_init_ring(struct psp_tee_device *tee)static void tee_destroy_ring (struct psp_tee_device *tee) { -unsigned int reg; -int ret; -if (!tee->rb_mgr.ring_start)return ;if (psp_dead) goto free_ring; - ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_DESTROY ,NULL , -TEE_DEFAULT_CMD_TIMEOUT , ®); -if (ret) { -dev_err (tee->dev,"tee: ring destroy command timed out, disabling TEE support\n" ); - psp_dead = true; - }else if (FIELD_GET (PSP_CMDRESP_STS , reg)) { -dev_err (tee->dev,"tee: ring destroy command failed (%#010lx)\n" , -FIELD_GET (PSP_CMDRESP_STS , reg)); - } +tee_send_destroy_cmd (tee); free_ring:tee_free_ring (tee);
Re: [PATCH v6 4/5] crypto: ccp - Factor out ring destroy handling to a helper
In reply to Mario Limonciello (AMD)Quoted 5 lines · Mario Limonciello (AMD)
The ring destroy command needs to be used in multiple places. Split out the code to a helper.
Tested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Quoted 64 lines
--- drivers/crypto/ccp/tee-dev.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-)drivers/crypto/ccp/tee-dev.c +24
-12@@ -86,6 +86,29 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)kfree (cmd); } +static bool tee_send_destroy_cmd (struct psp_tee_device *tee) +{ +unsigned int reg; +int ret; + + ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_DESTROY ,NULL , +TEE_DEFAULT_CMD_TIMEOUT , ®); +if (ret) { +dev_err (tee->dev,"tee: ring destroy command timed out, disabling TEE support\n" ); + psp_dead = true; +return false; + } + +if (FIELD_GET (PSP_CMDRESP_STS , reg)) { +dev_err (tee->dev,"tee: ring destroy command failed (%#010lx)\n" , +FIELD_GET (PSP_CMDRESP_STS , reg)); + psp_dead = true; +return false; + } + +return true; +} +static int tee_init_ring (struct psp_tee_device *tee) {int ring_size =MAX_RING_BUFFER_ENTRIES *sizeof (struct tee_ring_cmd );@@ -137,24 +160,13 @@ static int tee_init_ring(struct psp_tee_device *tee)static void tee_destroy_ring (struct psp_tee_device *tee) { -unsigned int reg; -int ret; -if (!tee->rb_mgr.ring_start)return ;if (psp_dead) goto free_ring; - ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_DESTROY ,NULL , -TEE_DEFAULT_CMD_TIMEOUT , ®); -if (ret) { -dev_err (tee->dev,"tee: ring destroy command timed out, disabling TEE support\n" ); - psp_dead = true; - }else if (FIELD_GET (PSP_CMDRESP_STS , reg)) { -dev_err (tee->dev,"tee: ring destroy command failed (%#010lx)\n" , -FIELD_GET (PSP_CMDRESP_STS , reg)); - } +tee_send_destroy_cmd (tee); free_ring:tee_free_ring (tee);
PATCH v6 5/5 crypto: ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT fails
In reply to Mario Limonciello (AMD)The hibernate resume sequence involves loading a resume kernel that is just used for loading the hibernate image before shifting back to the existing kernel.
During that hibernate resume sequence the resume kernel may have loaded the ccp driver. If this happens the resume kernel will also have called PSP_CMD_TEE_RING_INIT but it will never have called PSP_CMD_TEE_RING_DESTROY.
This is problematic because the existing kernel needs to re-initialize the ring. One could argue that the existing kernel should call destroy as part of restore() but there is no guarantee that the resume kernel did or didn't load the ccp driver. There is also no callback opportunity for the resume kernel to destroy before handing back control to the existing kernel.
Similar problems could potentially exist with the use of kdump and crash handling. I actually reproduced this issue like this:
1) rmmod ccp
2) hibernate the system
3) resume the system
4) modprobe ccp
The resume kernel will have loaded ccp but never destroyed and then when I try to modprobe it fails.
Because of these possible cases add a flow that checks the error code from the PSP_CMD_TEE_RING_INIT call and tries to call PSP_CMD_TEE_RING_DESTROY if it failed. If this succeeds then call PSP_CMD_TEE_RING_INIT again.
---
v5:
* Add retry=true for the retry case (Shyam)
v4:
* Add tag (Yijun)
* Move and rename PSP_TEE_STS_RING_BUSY (Ilpo)
v3:
* Add a comment (Tom)
* Add a define for busy condition (Shyam)
* Rename label (Shyam)
* Upgrade message to info (Shyam)
* Use a helper that validates result for destroy command (Shyam)
--- drivers/crypto/ccp/tee-dev.c | 14 ++++++++++++++ include/linux/psp.h | 1 + 2 files changed, 15 insertions(+)
drivers/crypto/ccp/tee-dev.c +14 -0
@@ -113,6 +113,7 @@ static int tee_init_ring(struct psp_tee_device *tee) {int ring_size =MAX_RING_BUFFER_ENTRIES *sizeof (struct tee_ring_cmd );struct tee_init_ring_cmd *cmd; +bool retry = false;unsigned int reg;int ret;
@@ -135,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)/* Send command buffer details to Trusted OS by writing to * CPU-PSP message registers */ +retry_init: ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_INIT , cmd,TEE_DEFAULT_CMD_TIMEOUT , ®);if (ret) {
@@ -145,6 +147,18 @@ static int tee_init_ring(struct psp_tee_device *tee) }if (FIELD_GET (PSP_CMDRESP_STS , reg)) { +/* +* During the hibernate resume sequence driver may have gotten loaded +* but the ring not properly destroyed. If the ring doesn't work, try +* to destroy and re-init once. +*/ +if (!retry &&FIELD_GET (PSP_CMDRESP_STS , reg) ==PSP_TEE_STS_RING_BUSY ) { +dev_info (tee->dev,"tee: ring init command failed with busy status, retrying\n" ); +if (tee_send_destroy_cmd (tee)) { + retry = true; + goto retry_init; + } + }dev_err (tee->dev,"tee: ring init command failed (%#010lx)\n" ,FIELD_GET (PSP_CMDRESP_STS , reg));tee_free_ring (tee);
include/linux/psp.h +1 -0
@@ -18,6 +18,7 @@* and should include an appropriate local definition in their source file. */ #define PSP_CMDRESP_STS GENMASK(15, 0) +#define PSP_TEE_STS_RING_BUSY 0x0000000d/* Ring already initialized */ #define PSP_CMDRESP_CMD GENMASK(23, 16)#define PSP_CMDRESP_RESERVED GENMASK(29, 24)#define PSP_CMDRESP_RECOVERY BIT(30)
Signature · 1 line
2.43.0
Re: [PATCH v6 5/5] crypto: ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT fails
Quoted 31 lines · Mario Limonciello (AMD)
The hibernate resume sequence involves loading a resume kernel that is just used for loading the hibernate image before shifting back to the existing kernel.
During that hibernate resume sequence the resume kernel may have loaded the ccp driver. If this happens the resume kernel will also have called
PSP_CMD_TEE_RING_INITbut it will never have calledPSP_CMD_TEE_RING_DESTROY.This is problematic because the existing kernel needs to re-initialize the ring. One could argue that the existing kernel should call destroy as part of
restore()but there is no guarantee that the resume kernel did or didn't load the ccp driver. There is also no callback opportunity for the resume kernel to destroy before handing back control to the existing kernel.Similar problems could potentially exist with the use of kdump and crash handling. I actually reproduced this issue like this:
1) rmmod ccp
2) hibernate the system
3) resume the system
4) modprobe ccpThe resume kernel will have loaded ccp but never destroyed and then when I try to modprobe it fails.
Because of these possible cases add a flow that checks the error code from the
PSP_CMD_TEE_RING_INITcall and tries to callPSP_CMD_TEE_RING_DESTROYif it failed. If this succeeds then callPSP_CMD_TEE_RING_INITagain.
Quoted 5 lines
Fixes f892a21f51162 ("crypto: ccp - use generic power management")Reported-by Lars Francke <[email protected]>Closes https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ open hereTested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Thanks,
Shyam
Quoted 68 lines
---
v5:
* Add retry=true for the retry case (Shyam)
v4:
* Add tag (Yijun)
* Move and renamePSP_TEE_STS_RING_BUSY(Ilpo)
v3:
* Add a comment (Tom)
* Add a define for busy condition (Shyam)
* Rename label (Shyam)
* Upgrade message to info (Shyam)
* Use a helper that validates result for destroy command (Shyam)--- drivers/crypto/ccp/tee-dev.c | 14 ++++++++++++++ include/linux/psp.h | 1 + 2 files changed, 15 insertions(+)drivers/crypto/ccp/tee-dev.c +14
-0@@ -113,6 +113,7 @@ static int tee_init_ring(struct psp_tee_device *tee) {int ring_size =MAX_RING_BUFFER_ENTRIES *sizeof (struct tee_ring_cmd );struct tee_init_ring_cmd *cmd; +bool retry = false;unsigned int reg;int ret;@@ -135,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)/* Send command buffer details to Trusted OS by writing to * CPU-PSP message registers */ +retry_init: ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_INIT , cmd,TEE_DEFAULT_CMD_TIMEOUT , ®);if (ret) {@@ -145,6 +147,18 @@ static int tee_init_ring(struct psp_tee_device *tee) }if (FIELD_GET (PSP_CMDRESP_STS , reg)) { +/* +* During the hibernate resume sequence driver may have gotten loaded +* but the ring not properly destroyed. If the ring doesn't work, try +* to destroy and re-init once. +*/ +if (!retry &&FIELD_GET (PSP_CMDRESP_STS , reg) ==PSP_TEE_STS_RING_BUSY ) { +dev_info (tee->dev,"tee: ring init command failed with busy status, retrying\n" ); +if (tee_send_destroy_cmd (tee)) { + retry = true; + goto retry_init; + } + }dev_err (tee->dev,"tee: ring init command failed (%#010lx)\n" ,FIELD_GET (PSP_CMDRESP_STS , reg));tee_free_ring (tee);include/linux/psp.h +1
-0@@ -18,6 +18,7 @@* and should include an appropriate local definition in their source file. */ #define PSP_CMDRESP_STS GENMASK(15, 0) +#define PSP_TEE_STS_RING_BUSY 0x0000000d/* Ring already initialized */ #define PSP_CMDRESP_CMD GENMASK(23, 16)#define PSP_CMDRESP_RESERVED GENMASK(29, 24)#define PSP_CMDRESP_RECOVERY BIT(30)
Re: [PATCH v6 5/5] crypto: ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT fails
In reply to Mario Limonciello (AMD)Quoted 31 lines · Mario Limonciello (AMD)
The hibernate resume sequence involves loading a resume kernel that is just used for loading the hibernate image before shifting back to the existing kernel.
During that hibernate resume sequence the resume kernel may have loaded the ccp driver. If this happens the resume kernel will also have called
PSP_CMD_TEE_RING_INITbut it will never have calledPSP_CMD_TEE_RING_DESTROY.This is problematic because the existing kernel needs to re-initialize the ring. One could argue that the existing kernel should call destroy as part of
restore()but there is no guarantee that the resume kernel did or didn't load the ccp driver. There is also no callback opportunity for the resume kernel to destroy before handing back control to the existing kernel.Similar problems could potentially exist with the use of kdump and crash handling. I actually reproduced this issue like this:
1) rmmod ccp
2) hibernate the system
3) resume the system
4) modprobe ccpThe resume kernel will have loaded ccp but never destroyed and then when I try to modprobe it fails.
Because of these possible cases add a flow that checks the error code from the
PSP_CMD_TEE_RING_INITcall and tries to callPSP_CMD_TEE_RING_DESTROYif it failed. If this succeeds then callPSP_CMD_TEE_RING_INITagain.
Quoted 5 lines
Fixes f892a21f51162 ("crypto: ccp - use generic power management")Reported-by Lars Francke <[email protected]>Closes https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ open hereTested-by Yijun Shen <[email protected]>Signed-off-by Mario Limonciello (AMD) <[email protected]>
Quoted 68 lines
---
v5:
* Add retry=true for the retry case (Shyam)
v4:
* Add tag (Yijun)
* Move and renamePSP_TEE_STS_RING_BUSY(Ilpo)
v3:
* Add a comment (Tom)
* Add a define for busy condition (Shyam)
* Rename label (Shyam)
* Upgrade message to info (Shyam)
* Use a helper that validates result for destroy command (Shyam)--- drivers/crypto/ccp/tee-dev.c | 14 ++++++++++++++ include/linux/psp.h | 1 + 2 files changed, 15 insertions(+)drivers/crypto/ccp/tee-dev.c +14
-0@@ -113,6 +113,7 @@ static int tee_init_ring(struct psp_tee_device *tee) {int ring_size =MAX_RING_BUFFER_ENTRIES *sizeof (struct tee_ring_cmd );struct tee_init_ring_cmd *cmd; +bool retry = false;unsigned int reg;int ret;@@ -135,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)/* Send command buffer details to Trusted OS by writing to * CPU-PSP message registers */ +retry_init: ret =psp_mailbox_command (tee->psp,PSP_CMD_TEE_RING_INIT , cmd,TEE_DEFAULT_CMD_TIMEOUT , ®);if (ret) {@@ -145,6 +147,18 @@ static int tee_init_ring(struct psp_tee_device *tee) }if (FIELD_GET (PSP_CMDRESP_STS , reg)) { +/* +* During the hibernate resume sequence driver may have gotten loaded +* but the ring not properly destroyed. If the ring doesn't work, try +* to destroy and re-init once. +*/ +if (!retry &&FIELD_GET (PSP_CMDRESP_STS , reg) ==PSP_TEE_STS_RING_BUSY ) { +dev_info (tee->dev,"tee: ring init command failed with busy status, retrying\n" ); +if (tee_send_destroy_cmd (tee)) { + retry = true; + goto retry_init; + } + }dev_err (tee->dev,"tee: ring init command failed (%#010lx)\n" ,FIELD_GET (PSP_CMDRESP_STS , reg));tee_free_ring (tee);include/linux/psp.h +1
-0@@ -18,6 +18,7 @@* and should include an appropriate local definition in their source file. */ #define PSP_CMDRESP_STS GENMASK(15, 0) +#define PSP_TEE_STS_RING_BUSY 0x0000000d/* Ring already initialized */ #define PSP_CMDRESP_CMD GENMASK(23, 16)#define PSP_CMDRESP_RESERVED GENMASK(29, 24)#define PSP_CMDRESP_RECOVERY BIT(30)
Ilpo Järvinen
maintainerQuoted 22 lines · Mario Limonciello (AMD)
Lars Francke reported that the PMF driver fails to work afer S4 with:
ccp 0000:c3:00.2: tee: command0x5timed out, disabling PSPThis is because there is a TA loaded to the TEE environment that is lost during S4. The TEE rings need to be reinitialized and the TA needs to be reloaded.
This series adds those flows to the PMF and CCP drivers.
v5->v6:
* Fix Tom's feedback on patch 3/5Mario Limonciello (AMD) (4):
crypto: ccp - Declare PSP dead ifPSP_CMD_TEE_RING_INITfails
crypto: ccp - Add an S4 restore flow
crypto: ccp - Factor out ring destroy handling to a helper
crypto: ccp - SendPSP_CMD_TEE_RING_DESTROYwhenPSP_CMD_TEE_RING_INIT
failsShyam Sundar S K (1):
platform/x86/amd/pmf: Prevent TEE errors after hibernate
Quoted 11 lines
drivers/crypto/ccp/psp-dev.c | 11 +++++ drivers/crypto/ccp/sp-dev.c | 12 ++++++ drivers/crypto/ccp/sp-dev.h | 3 ++ drivers/crypto/ccp/sp-pci.c | 16 ++++++- drivers/crypto/ccp/tee-dev.c | 56 ++++++++++++++++++------ drivers/crypto/ccp/tee-dev.h | 1 + drivers/platform/x86/amd/pmf/core.c | 62 ++++++++++++++++++++++++++- drivers/platform/x86/amd/pmf/pmf.h | 10 +++++ drivers/platform/x86/amd/pmf/tee-if.c | 12 ++---- include/linux/psp.h | 1 + 10 files changed, 161 insertions(+), 23 deletions(-)
I've applied this to the review-ilpo-next branch (and reorganized the NPU metrics to come after this series as these seemed to have a context conflict).
Signature · 1 line
i.