From 8ef5f6065d045bc61deb2d9380f3f57eed7355bd Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 14 Nov 2011 23:05:55 +0000 Subject: [PATCH] [arbel] Ensure hardware is quiescent when no interfaces are open Signed-off-by: Michael Brown --- src/drivers/infiniband/arbel.c | 337 ++++++++++++++++++++++++--------- src/drivers/infiniband/arbel.h | 26 ++- 2 files changed, 271 insertions(+), 92 deletions(-) diff --git a/src/drivers/infiniband/arbel.c b/src/drivers/infiniband/arbel.c index 7498bd4b..0a801856 100644 --- a/src/drivers/infiniband/arbel.c +++ b/src/drivers/infiniband/arbel.c @@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -1991,7 +1992,7 @@ static int arbel_start_firmware ( struct arbel *arbel ) { struct arbelprm_query_fw fw; struct arbelprm_access_lam lam; unsigned int fw_pages; - size_t fw_size; + size_t fw_len; physaddr_t fw_base; uint64_t eq_set_ci_base_addr; int rc; @@ -2019,17 +2020,22 @@ static int arbel_start_firmware ( struct arbel *arbel ) { arbel_cmd_enable_lam ( arbel, &lam ); /* Allocate firmware pages and map firmware area */ - fw_size = ( fw_pages * ARBEL_PAGE_SIZE ); - arbel->firmware_area = umalloc ( fw_size ); + fw_len = ( fw_pages * ARBEL_PAGE_SIZE ); if ( ! arbel->firmware_area ) { - rc = -ENOMEM; - goto err_alloc_fa; + arbel->firmware_len = fw_len; + arbel->firmware_area = umalloc ( arbel->firmware_len ); + if ( ! arbel->firmware_area ) { + rc = -ENOMEM; + goto err_alloc_fa; + } + } else { + assert ( arbel->firmware_len == fw_len ); } fw_base = user_to_phys ( arbel->firmware_area, 0 ); DBGC ( arbel, "Arbel %p firmware area at [%08lx,%08lx)\n", - arbel, fw_base, ( fw_base + fw_size ) ); + arbel, fw_base, ( fw_base + fw_len ) ); if ( ( rc = arbel_map_vpm ( arbel, arbel_cmd_map_fa, - 0, fw_base, fw_size ) ) != 0 ) { + 0, fw_base, fw_len ) ) != 0 ) { DBGC ( arbel, "Arbel %p could not map firmware: %s\n", arbel, strerror ( rc ) ); goto err_map_fa; @@ -2048,8 +2054,6 @@ static int arbel_start_firmware ( struct arbel *arbel ) { err_run_fw: arbel_cmd_unmap_fa ( arbel ); err_map_fa: - ufree ( arbel->firmware_area ); - arbel->firmware_area = UNULL; err_alloc_fa: err_query_fw: return rc; @@ -2067,10 +2071,9 @@ static void arbel_stop_firmware ( struct arbel *arbel ) { DBGC ( arbel, "Arbel %p FATAL could not stop firmware: %s\n", arbel, strerror ( rc ) ); /* Leak memory and return; at least we avoid corruption */ + arbel->firmware_area = UNULL; return; } - ufree ( arbel->firmware_area ); - arbel->firmware_area = UNULL; } /*************************************************************************** @@ -2180,6 +2183,7 @@ static int arbel_alloc_icm ( struct arbel *arbel, unsigned int log_num_uars, log_num_qps, log_num_srqs, log_num_ees; unsigned int log_num_cqs, log_num_mtts, log_num_mpts, log_num_rdbs; unsigned int log_num_eqs, log_num_mcs; + size_t icm_len, icm_aux_len; size_t len; physaddr_t icm_phys; int rc; @@ -2351,7 +2355,7 @@ static int arbel_alloc_icm ( struct arbel *arbel, /* Record amount of ICM to be allocated */ icm_offset = icm_align ( icm_offset, ARBEL_PAGE_SIZE ); - arbel->icm_len = icm_offset; + icm_len = icm_offset; /* User access region contexts * @@ -2376,24 +2380,29 @@ static int arbel_alloc_icm ( struct arbel *arbel, /* Get ICM auxiliary area size */ memset ( &icm_size, 0, sizeof ( icm_size ) ); - MLX_FILL_1 ( &icm_size, 1, value, arbel->icm_len ); + MLX_FILL_1 ( &icm_size, 1, value, icm_len ); if ( ( rc = arbel_cmd_set_icm_size ( arbel, &icm_size, &icm_aux_size ) ) != 0 ) { DBGC ( arbel, "Arbel %p could not set ICM size: %s\n", arbel, strerror ( rc ) ); goto err_set_icm_size; } - arbel->icm_aux_len = - ( MLX_GET ( &icm_aux_size, value ) * ARBEL_PAGE_SIZE ); + icm_aux_len = ( MLX_GET ( &icm_aux_size, value ) * ARBEL_PAGE_SIZE ); /* Allocate ICM data and auxiliary area */ DBGC ( arbel, "Arbel %p requires %zd kB ICM and %zd kB AUX ICM\n", - arbel, ( arbel->icm_len / 1024 ), - ( arbel->icm_aux_len / 1024 ) ); - arbel->icm = umalloc ( arbel->icm_len + arbel->icm_aux_len ); + arbel, ( icm_len / 1024 ), ( icm_aux_len / 1024 ) ); if ( ! arbel->icm ) { - rc = -ENOMEM; - goto err_alloc_icm; + arbel->icm_len = icm_len; + arbel->icm_aux_len = icm_aux_len; + arbel->icm = umalloc ( arbel->icm_len + arbel->icm_aux_len ); + if ( ! arbel->icm ) { + rc = -ENOMEM; + goto err_alloc_icm; + } + } else { + assert ( arbel->icm_len == icm_len ); + assert ( arbel->icm_aux_len == icm_aux_len ); } icm_phys = user_to_phys ( arbel->icm, 0 ); @@ -2459,8 +2468,6 @@ static int arbel_alloc_icm ( struct arbel *arbel, free_dma ( arbel->db_rec, ARBEL_PAGE_SIZE ); arbel->db_rec= NULL; err_alloc_doorbell: - ufree ( arbel->icm ); - arbel->icm = UNULL; err_alloc_icm: err_set_icm_size: return rc; @@ -2483,17 +2490,41 @@ static void arbel_free_icm ( struct arbel *arbel ) { arbel_cmd_unmap_icm_aux ( arbel ); free_dma ( arbel->db_rec, ARBEL_PAGE_SIZE ); arbel->db_rec = NULL; - ufree ( arbel->icm ); - arbel->icm = UNULL; } /*************************************************************************** * - * Initialisation + * Initialisation and teardown * *************************************************************************** */ +/** + * Reset device + * + * @v arbel Arbel device + */ +static void arbel_reset ( struct arbel *arbel ) { + struct pci_device *pci = arbel->pci; + struct pci_config_backup backup; + static const uint8_t backup_exclude[] = + PCI_CONFIG_BACKUP_EXCLUDE ( 0x58, 0x5c ); + uint16_t vendor; + unsigned int i; + + /* Perform device reset and preserve PCI configuration */ + pci_backup ( pci, &backup, backup_exclude ); + writel ( ARBEL_RESET_MAGIC, + ( arbel->config + ARBEL_RESET_OFFSET ) ); + for ( i = 0 ; i < ARBEL_RESET_WAIT_TIME_MS ; i++ ) { + mdelay ( 1 ); + pci_read_config_word ( pci, PCI_VENDOR_ID, &vendor ); + if ( vendor != 0xffff ) + break; + } + pci_restore ( pci, &backup, backup_exclude ); +} + /** * Set up memory protection table * @@ -2572,6 +2603,115 @@ static int arbel_configure_special_qps ( struct arbel *arbel ) { return 0; } +/** + * Start Arbel device + * + * @v arbel Arbel device + * @v running Firmware is already running + * @ret rc Return status code + */ +static int arbel_start ( struct arbel *arbel, int running ) { + struct arbelprm_init_hca init_hca; + unsigned int i; + int rc; + + /* Start firmware if not already running */ + if ( ! running ) { + if ( ( rc = arbel_start_firmware ( arbel ) ) != 0 ) + goto err_start_firmware; + } + + /* Allocate ICM */ + memset ( &init_hca, 0, sizeof ( init_hca ) ); + if ( ( rc = arbel_alloc_icm ( arbel, &init_hca ) ) != 0 ) + goto err_alloc_icm; + + /* Initialise HCA */ + if ( ( rc = arbel_cmd_init_hca ( arbel, &init_hca ) ) != 0 ) { + DBGC ( arbel, "Arbel %p could not initialise HCA: %s\n", + arbel, strerror ( rc ) ); + goto err_init_hca; + } + + /* Set up memory protection */ + if ( ( rc = arbel_setup_mpt ( arbel ) ) != 0 ) + goto err_setup_mpt; + for ( i = 0 ; i < ARBEL_NUM_PORTS ; i++ ) + arbel->ibdev[i]->rdma_key = arbel->lkey; + + /* Set up event queue */ + if ( ( rc = arbel_create_eq ( arbel ) ) != 0 ) + goto err_create_eq; + + /* Configure special QPs */ + if ( ( rc = arbel_configure_special_qps ( arbel ) ) != 0 ) + goto err_conf_special_qps; + + return 0; + + err_conf_special_qps: + arbel_destroy_eq ( arbel ); + err_create_eq: + err_setup_mpt: + arbel_cmd_close_hca ( arbel ); + err_init_hca: + arbel_free_icm ( arbel ); + err_alloc_icm: + arbel_stop_firmware ( arbel ); + err_start_firmware: + return rc; +} + +/** + * Stop Arbel device + * + * @v arbel Arbel device + */ +static void arbel_stop ( struct arbel *arbel ) { + arbel_destroy_eq ( arbel ); + arbel_cmd_close_hca ( arbel ); + arbel_free_icm ( arbel ); + arbel_stop_firmware ( arbel ); + arbel_reset ( arbel ); +} + +/** + * Open Arbel device + * + * @v arbel Arbel device + * @ret rc Return status code + */ +static int arbel_open ( struct arbel *arbel ) { + int rc; + + /* Start device if applicable */ + if ( arbel->open_count == 0 ) { + if ( ( rc = arbel_start ( arbel, 0 ) ) != 0 ) + return rc; + } + + /* Increment open counter */ + arbel->open_count++; + + return 0; +} + +/** + * Close Arbel device + * + * @v arbel Arbel device + */ +static void arbel_close ( struct arbel *arbel ) { + + /* Decrement open counter */ + assert ( arbel->open_count != 0 ); + arbel->open_count--; + + /* Stop device if applicable */ + if ( arbel->open_count == 0 ) + arbel_stop ( arbel ); +} + /*************************************************************************** * * Infiniband link-layer operations @@ -2585,11 +2725,16 @@ static int arbel_configure_special_qps ( struct arbel *arbel ) { * @v ibdev Infiniband device * @ret rc Return status code */ -static int arbel_open ( struct ib_device *ibdev ) { +static int arbel_ib_open ( struct ib_device *ibdev ) { struct arbel *arbel = ib_get_drvdata ( ibdev ); struct arbelprm_init_ib init_ib; int rc; + /* Open hardware */ + if ( ( rc = arbel_open ( arbel ) ) != 0 ) + goto err_open; + + /* Initialise IB */ memset ( &init_ib, 0, sizeof ( init_ib ) ); MLX_FILL_3 ( &init_ib, 0, mtu_cap, ARBEL_MTU_2048, @@ -2601,13 +2746,18 @@ static int arbel_open ( struct ib_device *ibdev ) { &init_ib ) ) != 0 ) { DBGC ( arbel, "Arbel %p port %d could not intialise IB: %s\n", arbel, ibdev->port, strerror ( rc ) ); - return rc; + goto err_init_ib; } /* Update MAD parameters */ ib_smc_update ( ibdev, arbel_mad ); return 0; + + err_init_ib: + arbel_close ( arbel ); + err_open: + return rc; } /** @@ -2615,15 +2765,19 @@ static int arbel_open ( struct ib_device *ibdev ) { * * @v ibdev Infiniband device */ -static void arbel_close ( struct ib_device *ibdev ) { +static void arbel_ib_close ( struct ib_device *ibdev ) { struct arbel *arbel = ib_get_drvdata ( ibdev ); int rc; + /* Close IB */ if ( ( rc = arbel_cmd_close_ib ( arbel, ibdev->port ) ) != 0 ) { DBGC ( arbel, "Arbel %p port %d could not close IB: %s\n", arbel, ibdev->port, strerror ( rc ) ); /* Nothing we can do about this */ } + + /* Close hardware */ + arbel_close ( arbel ); } /** @@ -2753,8 +2907,8 @@ static struct ib_device_operations arbel_ib_operations = { .post_recv = arbel_post_recv, .poll_cq = arbel_poll_cq, .poll_eq = arbel_poll_eq, - .open = arbel_open, - .close = arbel_close, + .open = arbel_ib_open, + .close = arbel_ib_close, .mcast_attach = arbel_mcast_attach, .mcast_detach = arbel_mcast_detach, .set_port_info = arbel_inform_sma, @@ -2768,6 +2922,52 @@ static struct ib_device_operations arbel_ib_operations = { *************************************************************************** */ +/** + * Allocate Arbel device + * + * @ret arbel Arbel device + */ +static struct arbel * arbel_alloc ( void ) { + struct arbel *arbel; + + /* Allocate Arbel device */ + arbel = zalloc ( sizeof ( *arbel ) ); + if ( ! arbel ) + goto err_arbel; + + /* Allocate space for mailboxes */ + arbel->mailbox_in = malloc_dma ( ARBEL_MBOX_SIZE, ARBEL_MBOX_ALIGN ); + if ( ! arbel->mailbox_in ) + goto err_mailbox_in; + arbel->mailbox_out = malloc_dma ( ARBEL_MBOX_SIZE, ARBEL_MBOX_ALIGN ); + if ( ! arbel->mailbox_out ) + goto err_mailbox_out; + + return arbel; + + free_dma ( arbel->mailbox_out, ARBEL_MBOX_SIZE ); + err_mailbox_out: + free_dma ( arbel->mailbox_in, ARBEL_MBOX_SIZE ); + err_mailbox_in: + free ( arbel ); + err_arbel: + return NULL; +} + +/** + * Free Arbel device + * + * @v arbel Arbel device + */ +static void arbel_free ( struct arbel *arbel ) { + + ufree ( arbel->icm ); + ufree ( arbel->firmware_area ); + free_dma ( arbel->mailbox_out, ARBEL_MBOX_SIZE ); + free_dma ( arbel->mailbox_in, ARBEL_MBOX_SIZE ); + free ( arbel ); +} + /** * Probe PCI device * @@ -2778,17 +2978,17 @@ static struct ib_device_operations arbel_ib_operations = { static int arbel_probe ( struct pci_device *pci ) { struct arbel *arbel; struct ib_device *ibdev; - struct arbelprm_init_hca init_hca; int i; int rc; /* Allocate Arbel device */ - arbel = zalloc ( sizeof ( *arbel ) ); + arbel = arbel_alloc(); if ( ! arbel ) { rc = -ENOMEM; - goto err_alloc_arbel; + goto err_alloc; } pci_set_drvdata ( pci, arbel ); + arbel->pci = pci; /* Allocate Infiniband devices */ for ( i = 0 ; i < ARBEL_NUM_PORTS ; i++ ) { @@ -2814,17 +3014,8 @@ static int arbel_probe ( struct pci_device *pci ) { ARBEL_PCI_UAR_IDX * ARBEL_PCI_UAR_SIZE ), ARBEL_PCI_UAR_SIZE ); - /* Allocate space for mailboxes */ - arbel->mailbox_in = malloc_dma ( ARBEL_MBOX_SIZE, ARBEL_MBOX_ALIGN ); - if ( ! arbel->mailbox_in ) { - rc = -ENOMEM; - goto err_mailbox_in; - } - arbel->mailbox_out = malloc_dma ( ARBEL_MBOX_SIZE, ARBEL_MBOX_ALIGN ); - if ( ! arbel->mailbox_out ) { - rc = -ENOMEM; - goto err_mailbox_out; - } + /* Reset device */ + arbel_reset ( arbel ); /* Start firmware */ if ( ( rc = arbel_start_firmware ( arbel ) ) != 0 ) @@ -2834,31 +3025,9 @@ static int arbel_probe ( struct pci_device *pci ) { if ( ( rc = arbel_get_limits ( arbel ) ) != 0 ) goto err_get_limits; - /* Allocate ICM */ - memset ( &init_hca, 0, sizeof ( init_hca ) ); - if ( ( rc = arbel_alloc_icm ( arbel, &init_hca ) ) != 0 ) - goto err_alloc_icm; - - /* Initialise HCA */ - if ( ( rc = arbel_cmd_init_hca ( arbel, &init_hca ) ) != 0 ) { - DBGC ( arbel, "Arbel %p could not initialise HCA: %s\n", - arbel, strerror ( rc ) ); - goto err_init_hca; - } - - /* Set up memory protection */ - if ( ( rc = arbel_setup_mpt ( arbel ) ) != 0 ) - goto err_setup_mpt; - for ( i = 0 ; i < ARBEL_NUM_PORTS ; i++ ) - arbel->ibdev[i]->rdma_key = arbel->lkey; - - /* Set up event queue */ - if ( ( rc = arbel_create_eq ( arbel ) ) != 0 ) - goto err_create_eq; - - /* Configure special QPs */ - if ( ( rc = arbel_configure_special_qps ( arbel ) ) != 0 ) - goto err_conf_special_qps; + /* Start device */ + if ( ( rc = arbel_start ( arbel, 1 ) ) != 0 ) + goto err_start; /* Initialise parameters using SMC */ for ( i = 0 ; i < ARBEL_NUM_PORTS ; i++ ) @@ -2874,33 +3043,27 @@ static int arbel_probe ( struct pci_device *pci ) { } } + /* Leave device quiescent until opened */ + if ( arbel->open_count == 0 ) + arbel_stop ( arbel ); + return 0; i = ARBEL_NUM_PORTS; err_register_ibdev: for ( i-- ; i >= 0 ; i-- ) unregister_ibdev ( arbel->ibdev[i] ); - err_conf_special_qps: - arbel_destroy_eq ( arbel ); - err_create_eq: - err_setup_mpt: - arbel_cmd_close_hca ( arbel ); - err_init_hca: - arbel_free_icm ( arbel ); - err_alloc_icm: + arbel_stop ( arbel ); + err_start: err_get_limits: arbel_stop_firmware ( arbel ); err_start_firmware: - free_dma ( arbel->mailbox_out, ARBEL_MBOX_SIZE ); - err_mailbox_out: - free_dma ( arbel->mailbox_in, ARBEL_MBOX_SIZE ); - err_mailbox_in: i = ARBEL_NUM_PORTS; err_alloc_ibdev: for ( i-- ; i >= 0 ; i-- ) ibdev_put ( arbel->ibdev[i] ); - free ( arbel ); - err_alloc_arbel: + arbel_free ( arbel ); + err_alloc: return rc; } @@ -2915,15 +3078,9 @@ static void arbel_remove ( struct pci_device *pci ) { for ( i = ( ARBEL_NUM_PORTS - 1 ) ; i >= 0 ; i-- ) unregister_ibdev ( arbel->ibdev[i] ); - arbel_destroy_eq ( arbel ); - arbel_cmd_close_hca ( arbel ); - arbel_free_icm ( arbel ); - arbel_stop_firmware ( arbel ); - free_dma ( arbel->mailbox_out, ARBEL_MBOX_SIZE ); - free_dma ( arbel->mailbox_in, ARBEL_MBOX_SIZE ); for ( i = ( ARBEL_NUM_PORTS - 1 ) ; i >= 0 ; i-- ) ibdev_put ( arbel->ibdev[i] ); - free ( arbel ); + arbel_free ( arbel ); } static struct pci_device_id arbel_nics[] = { diff --git a/src/drivers/infiniband/arbel.h b/src/drivers/infiniband/arbel.h index 40a749a0..c0303f1b 100644 --- a/src/drivers/infiniband/arbel.h +++ b/src/drivers/infiniband/arbel.h @@ -31,6 +31,11 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ARBEL_PCI_UAR_IDX 1 #define ARBEL_PCI_UAR_SIZE 0x1000 +/* Device reset */ +#define ARBEL_RESET_OFFSET 0x0f0010 +#define ARBEL_RESET_MAGIC 0x01000000UL +#define ARBEL_RESET_WAIT_TIME_MS 1000 + /* UAR context table (UCE) resource types */ #define ARBEL_UAR_RES_NONE 0x00 #define ARBEL_UAR_RES_CQ_CI 0x01 @@ -458,6 +463,8 @@ typedef uint32_t arbel_bitmask_t; /** An Arbel device */ struct arbel { + /** PCI device */ + struct pci_device *pci; /** PCI configuration registers */ void *config; /** PCI user Access Region */ @@ -470,13 +477,28 @@ struct arbel { /** Command output mailbox */ void *mailbox_out; - /** Firmware area in external memory */ + /** Device open request counter */ + unsigned int open_count; + + /** Firmware size */ + size_t firmware_len; + /** Firmware area in external memory + * + * This is allocated when first needed, and freed only on + * final teardown, in order to avoid memory map changes at + * runtime. + */ userptr_t firmware_area; /** ICM size */ size_t icm_len; /** ICM AUX size */ size_t icm_aux_len; - /** ICM area */ + /** ICM area + * + * This is allocated when first needed, and freed only on + * final teardown, in order to avoid memory map changes at + * runtime. + */ userptr_t icm; /** Offset within ICM of doorbell records */ size_t db_rec_offset;