diff options
Diffstat (limited to 'drivers')
214 files changed, 17805 insertions, 4624 deletions
diff --git a/drivers/acpi/bgrt.c b/drivers/acpi/bgrt.c index 8cf6c46e99f..6680df36b96 100644 --- a/drivers/acpi/bgrt.c +++ b/drivers/acpi/bgrt.c @@ -11,6 +11,7 @@ #include <linux/init.h> #include <linux/device.h> #include <linux/sysfs.h> +#include <linux/io.h> #include <acpi/acpi.h> #include <acpi/acpi_bus.h> diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 06527c52661..74ee4ab577b 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -93,11 +93,9 @@ static int acpi_sleep_prepare(u32 acpi_state) #ifdef CONFIG_ACPI_SLEEP /* do we have a wakeup address for S2 and S3? */ if (acpi_state == ACPI_STATE_S3) { - if (!acpi_wakeup_address) { + if (!acpi_wakeup_address) return -EFAULT; - } - acpi_set_firmware_waking_vector( - (acpi_physical_address)acpi_wakeup_address); + acpi_set_firmware_waking_vector(acpi_wakeup_address); } ACPI_FLUSH_CPU_CACHE(); diff --git a/drivers/atm/solos-pci.c b/drivers/atm/solos-pci.c index e8cd652d201..98510931c81 100644 --- a/drivers/atm/solos-pci.c +++ b/drivers/atm/solos-pci.c @@ -984,6 +984,7 @@ static uint32_t fpga_tx(struct solos_card *card) } else if (skb && card->using_dma) { SKB_CB(skb)->dma_addr = pci_map_single(card->dev, skb->data, skb->len, PCI_DMA_TODEVICE); + card->tx_skb[port] = skb; iowrite32(SKB_CB(skb)->dma_addr, card->config_regs + TX_DMA_ADDR(port)); } @@ -1152,7 +1153,8 @@ static int fpga_probe(struct pci_dev *dev, const struct pci_device_id *id) db_fpga_upgrade = db_firmware_upgrade = 0; } - if (card->fpga_version >= DMA_SUPPORTED){ + if (card->fpga_version >= DMA_SUPPORTED) { + pci_set_master(dev); card->using_dma = 1; } else { card->using_dma = 0; diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c index 5f6b2478bf1..fa6bf5279d2 100644 --- a/drivers/base/regmap/regmap-i2c.c +++ b/drivers/base/regmap/regmap-i2c.c @@ -42,7 +42,7 @@ static int regmap_i2c_gather_write(void *context, /* If the I2C controller can't do a gather tell the core, it * will substitute in a linear write for us. */ - if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_PROTOCOL_MANGLING)) + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_NOSTART)) return -ENOTSUPP; xfer[0].addr = i2c->addr; diff --git a/drivers/block/drbd/drbd_actlog.c b/drivers/block/drbd/drbd_actlog.c index cf0e63dd97d..e54e31b02b8 100644 --- a/drivers/block/drbd/drbd_actlog.c +++ b/drivers/block/drbd/drbd_actlog.c @@ -65,39 +65,80 @@ struct drbd_atodb_wait { int w_al_write_transaction(struct drbd_conf *, struct drbd_work *, int); +void *drbd_md_get_buffer(struct drbd_conf *mdev) +{ + int r; + + wait_event(mdev->misc_wait, + (r = atomic_cmpxchg(&mdev->md_io_in_use, 0, 1)) == 0 || + mdev->state.disk <= D_FAILED); + + return r ? NULL : page_address(mdev->md_io_page); +} + +void drbd_md_put_buffer(struct drbd_conf *mdev) +{ + if (atomic_dec_and_test(&mdev->md_io_in_use)) + wake_up(&mdev->misc_wait); +} + +static bool md_io_allowed(struct drbd_conf *mdev) +{ + enum drbd_disk_state ds = mdev->state.disk; + return ds >= D_NEGOTIATING || ds == D_ATTACHING; +} + +void wait_until_done_or_disk_failure(struct drbd_conf *mdev, struct drbd_backing_dev *bdev, + unsigned int *done) +{ + long dt = bdev->dc.disk_timeout * HZ / 10; + if (dt == 0) + dt = MAX_SCHEDULE_TIMEOUT; + + dt = wait_event_timeout(mdev->misc_wait, *done || !md_io_allowed(mdev), dt); + if (dt == 0) + dev_err(DEV, "meta-data IO operation timed out\n"); +} + static int _drbd_md_sync_page_io(struct drbd_conf *mdev, struct drbd_backing_dev *bdev, struct page *page, sector_t sector, int rw, int size) { struct bio *bio; - struct drbd_md_io md_io; int ok; - md_io.mdev = mdev; - init_completion(&md_io.event); - md_io.error = 0; + mdev->md_io.done = 0; + mdev->md_io.error = -ENODEV; if ((rw & WRITE) && !test_bit(MD_NO_FUA, &mdev->flags)) rw |= REQ_FUA | REQ_FLUSH; rw |= REQ_SYNC; - bio = bio_alloc(GFP_NOIO, 1); + bio = bio_alloc_drbd(GFP_NOIO); bio->bi_bdev = bdev->md_bdev; bio->bi_sector = sector; ok = (bio_add_page(bio, page, size, 0) == size); if (!ok) goto out; - bio->bi_private = &md_io; + bio->bi_private = &mdev->md_io; bio->bi_end_io = drbd_md_io_complete; bio->bi_rw = rw; + if (!get_ldev_if_state(mdev, D_ATTACHING)) { /* Corresponding put_ldev in drbd_md_io_complete() */ + dev_err(DEV, "ASSERT FAILED: get_ldev_if_state() == 1 in _drbd_md_sync_page_io()\n"); + ok = 0; + goto out; + } + + bio_get(bio); /* one bio_put() is in the completion handler */ + atomic_inc(&mdev->md_io_in_use); /* drbd_md_put_buffer() is in the completion handler */ if (drbd_insert_fault(mdev, (rw & WRITE) ? DRBD_FAULT_MD_WR : DRBD_FAULT_MD_RD)) bio_endio(bio, -EIO); else submit_bio(rw, bio); - wait_for_completion(&md_io.event); - ok = bio_flagged(bio, BIO_UPTODATE) && md_io.error == 0; + wait_until_done_or_disk_failure(mdev, bdev, &mdev->md_io.done); + ok = bio_flagged(bio, BIO_UPTODATE) && mdev->md_io.error == 0; out: bio_put(bio); @@ -111,7 +152,7 @@ int drbd_md_sync_page_io(struct drbd_conf *mdev, struct drbd_backing_dev *bdev, int offset = 0; struct page *iop = mdev->md_io_page; - D_ASSERT(mutex_is_locked(&mdev->md_io_mutex)); + D_ASSERT(atomic_read(&mdev->md_io_in_use) == 1); BUG_ON(!bdev->md_bdev); @@ -328,8 +369,13 @@ w_al_write_transaction(struct drbd_conf *mdev, struct drbd_work *w, int unused) return 1; } - mutex_lock(&mdev->md_io_mutex); /* protects md_io_buffer, al_tr_cycle, ... */ - buffer = (struct al_transaction *)page_address(mdev->md_io_page); + buffer = drbd_md_get_buffer(mdev); /* protects md_io_buffer, al_tr_cycle, ... */ + if (!buffer) { + dev_err(DEV, "disk failed while waiting for md_io buffer\n"); + complete(&((struct update_al_work *)w)->event); + put_ldev(mdev); + return 1; + } buffer->magic = __constant_cpu_to_be32(DRBD_MAGIC); buffer->tr_number = cpu_to_be32(mdev->al_tr_number); @@ -374,7 +420,7 @@ w_al_write_transaction(struct drbd_conf *mdev, struct drbd_work *w, int unused) D_ASSERT(mdev->al_tr_pos < MD_AL_MAX_SIZE); mdev->al_tr_number++; - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); complete(&((struct update_al_work *)w)->event); put_ldev(mdev); @@ -443,8 +489,9 @@ int drbd_al_read_log(struct drbd_conf *mdev, struct drbd_backing_dev *bdev) /* lock out all other meta data io for now, * and make sure the page is mapped. */ - mutex_lock(&mdev->md_io_mutex); - buffer = page_address(mdev->md_io_page); + buffer = drbd_md_get_buffer(mdev); + if (!buffer) + return 0; /* Find the valid transaction in the log */ for (i = 0; i <= mx; i++) { @@ -452,7 +499,7 @@ int drbd_al_read_log(struct drbd_conf *mdev, struct drbd_backing_dev *bdev) if (rv == 0) continue; if (rv == -1) { - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); return 0; } cnr = be32_to_cpu(buffer->tr_number); @@ -478,7 +525,7 @@ int drbd_al_read_log(struct drbd_conf *mdev, struct drbd_backing_dev *bdev) if (!found_valid) { dev_warn(DEV, "No usable activity log found.\n"); - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); return 1; } @@ -493,7 +540,7 @@ int drbd_al_read_log(struct drbd_conf *mdev, struct drbd_backing_dev *bdev) rv = drbd_al_read_tr(mdev, bdev, buffer, i); ERR_IF(rv == 0) goto cancel; if (rv == -1) { - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); return 0; } @@ -534,7 +581,7 @@ cancel: mdev->al_tr_pos = 0; /* ok, we are done with it */ - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); dev_info(DEV, "Found %d transactions (%d active extents) in activity log.\n", transactions, active_extents); @@ -671,16 +718,20 @@ static void drbd_try_clear_on_disk_bm(struct drbd_conf *mdev, sector_t sector, else ext->rs_failed += count; if (ext->rs_left < ext->rs_failed) { - dev_err(DEV, "BAD! sector=%llus enr=%u rs_left=%d " - "rs_failed=%d count=%d\n", + dev_warn(DEV, "BAD! sector=%llus enr=%u rs_left=%d " + "rs_failed=%d count=%d cstate=%s\n", (unsigned long long)sector, ext->lce.lc_number, ext->rs_left, - ext->rs_failed, count); - dump_stack(); - - lc_put(mdev->resync, &ext->lce); - drbd_force_state(mdev, NS(conn, C_DISCONNECTING)); - return; + ext->rs_failed, count, + drbd_conn_str(mdev->state.conn)); + + /* We don't expect to be able to clear more bits + * than have been set when we originally counted + * the set bits to cache that value in ext->rs_left. + * Whatever the reason (disconnect during resync, + * delayed local completion of an application write), + * try to fix it up by recounting here. */ + ext->rs_left = drbd_bm_e_weight(mdev, enr); } } else { /* Normally this element should be in the cache, @@ -1192,6 +1243,7 @@ int drbd_rs_del_all(struct drbd_conf *mdev) put_ldev(mdev); } spin_unlock_irq(&mdev->al_lock); + wake_up(&mdev->al_wait); return 0; } diff --git a/drivers/block/drbd/drbd_bitmap.c b/drivers/block/drbd/drbd_bitmap.c index 3030201c69d..b5c5ff53cb5 100644 --- a/drivers/block/drbd/drbd_bitmap.c +++ b/drivers/block/drbd/drbd_bitmap.c @@ -205,7 +205,7 @@ void drbd_bm_unlock(struct drbd_conf *mdev) static void bm_store_page_idx(struct page *page, unsigned long idx) { BUG_ON(0 != (idx & ~BM_PAGE_IDX_MASK)); - page_private(page) |= idx; + set_page_private(page, idx); } static unsigned long bm_page_to_idx(struct page *page) @@ -886,12 +886,21 @@ void drbd_bm_clear_all(struct drbd_conf *mdev) struct bm_aio_ctx { struct drbd_conf *mdev; atomic_t in_flight; - struct completion done; + unsigned int done; unsigned flags; #define BM_AIO_COPY_PAGES 1 int error; + struct kref kref; }; +static void bm_aio_ctx_destroy(struct kref *kref) +{ + struct bm_aio_ctx *ctx = container_of(kref, struct bm_aio_ctx, kref); + + put_ldev(ctx->mdev); + kfree(ctx); +} + /* bv_page may be a copy, or may be the original */ static void bm_async_io_complete(struct bio *bio, int error) { @@ -930,20 +939,21 @@ static void bm_async_io_complete(struct bio *bio, int error) bm_page_unlock_io(mdev, idx); - /* FIXME give back to page pool */ if (ctx->flags & BM_AIO_COPY_PAGES) - put_page(bio->bi_io_vec[0].bv_page); + mempool_free(bio->bi_io_vec[0].bv_page, drbd_md_io_page_pool); bio_put(bio); - if (atomic_dec_and_test(&ctx->in_flight)) - complete(&ctx->done); + if (atomic_dec_and_test(&ctx->in_flight)) { + ctx->done = 1; + wake_up(&mdev->misc_wait); + kref_put(&ctx->kref, &bm_aio_ctx_destroy); + } } static void bm_page_io_async(struct bm_aio_ctx *ctx, int page_nr, int rw) __must_hold(local) { - /* we are process context. we always get a bio */ - struct bio *bio = bio_alloc(GFP_KERNEL, 1); + struct bio *bio = bio_alloc_drbd(GFP_NOIO); struct drbd_conf *mdev = ctx->mdev; struct drbd_bitmap *b = mdev->bitmap; struct page *page; @@ -966,10 +976,8 @@ static void bm_page_io_async(struct bm_aio_ctx *ctx, int page_nr, int rw) __must bm_set_page_unchanged(b->bm_pages[page_nr]); if (ctx->flags & BM_AIO_COPY_PAGES) { - /* FIXME alloc_page is good enough for now, but actually needs - * to use pre-allocated page pool */ void *src, *dest; - page = alloc_page(__GFP_HIGHMEM|__GFP_WAIT); + page = mempool_alloc(drbd_md_io_page_pool, __GFP_HIGHMEM|__GFP_WAIT); dest = kmap_atomic(page); src = kmap_atomic(b->bm_pages[page_nr]); memcpy(dest, src, PAGE_SIZE); @@ -981,6 +989,8 @@ static void bm_page_io_async(struct bm_aio_ctx *ctx, int page_nr, int rw) __must bio->bi_bdev = mdev->ldev->md_bdev; bio->bi_sector = on_disk_sector; + /* bio_add_page of a single page to an empty bio will always succeed, + * according to api. Do we want to assert that? */ bio_add_page(bio, page, len, 0); bio->bi_private = ctx; bio->bi_end_io = bm_async_io_complete; @@ -999,14 +1009,9 @@ static void bm_page_io_async(struct bm_aio_ctx *ctx, int page_nr, int rw) __must /* * bm_rw: read/write the whole bitmap from/to its on disk location. */ -static int bm_rw(struct drbd_conf *mdev, int rw, unsigned lazy_writeout_upper_idx) __must_hold(local) +static int bm_rw(struct drbd_conf *mdev, int rw, unsigned flags, unsigned lazy_writeout_upper_idx) __must_hold(local) { - struct bm_aio_ctx ctx = { - .mdev = mdev, - .in_flight = ATOMIC_INIT(1), - .done = COMPLETION_INITIALIZER_ONSTACK(ctx.done), - .flags = lazy_writeout_upper_idx ? BM_AIO_COPY_PAGES : 0, - }; + struct bm_aio_ctx *ctx; struct drbd_bitmap *b = mdev->bitmap; int num_pages, i, count = 0; unsigned long now; @@ -1021,7 +1026,27 @@ static int bm_rw(struct drbd_conf *mdev, int rw, unsigned lazy_writeout_upper_id * For lazy writeout, we don't care for ongoing changes to the bitmap, * as we submit copies of pages anyways. */ - if (!ctx.flags) + + ctx = kmalloc(sizeof(struct bm_aio_ctx), GFP_NOIO); + if (!ctx) + return -ENOMEM; + + *ctx = (struct bm_aio_ctx) { + .mdev = mdev, + .in_flight = ATOMIC_INIT(1), + .done = 0, + .flags = flags, + .error = 0, + .kref = { ATOMIC_INIT(2) }, + }; + + if (!get_ldev_if_state(mdev, D_ATTACHING)) { /* put is in bm_aio_ctx_destroy() */ + dev_err(DEV, "ASSERT FAILED: get_ldev_if_state() == 1 in bm_rw()\n"); + kfree(ctx); + return -ENODEV; + } + + if (!ctx->flags) WARN_ON(!(BM_LOCKED_MASK & b->bm_flags)); num_pages = b->bm_number_of_pages; @@ -1046,29 +1071,38 @@ static int bm_rw(struct drbd_conf *mdev, int rw, unsigned lazy_writeout_upper_id continue; } } - atomic_inc(&ctx.in_flight); - bm_page_io_async(&ctx, i, rw); + atomic_inc(&ctx->in_flight); + bm_page_io_async(ctx, i, rw); ++count; cond_resched(); } /* - * We initialize ctx.in_flight to one to make sure bm_async_io_complete - * will not complete() early, and decrement / test it here. If there + * We initialize ctx->in_flight to one to make sure bm_async_io_complete + * will not set ctx->done early, and decrement / test it here. If there * are still some bios in flight, we need to wait for them here. + * If all IO is done already (or nothing had been submitted), there is + * no need to wait. Still, we need to put the kref associated with the + * "in_flight reached zero, all done" event. */ - if (!atomic_dec_and_test(&ctx.in_flight)) - wait_for_completion(&ctx.done); + if (!atomic_dec_and_test(&ctx->in_flight)) + wait_until_done_or_disk_failure(mdev, mdev->ldev, &ctx->done); + else + kref_put(&ctx->kref, &bm_aio_ctx_destroy); + dev_info(DEV, "bitmap %s of %u pages took %lu jiffies\n", rw == WRITE ? "WRITE" : "READ", count, jiffies - now); - if (ctx.error) { + if (ctx->error) { dev_alert(DEV, "we had at least one MD IO ERROR during bitmap IO\n"); drbd_chk_io_error(mdev, 1, true); - err = -EIO; /* ctx.error ? */ + err = -EIO; /* ctx->error ? */ } + if (atomic_read(&ctx->in_flight)) + err = -EIO; /* Disk failed during IO... */ + now = jiffies; if (rw == WRITE) { drbd_md_flush(mdev); @@ -1082,6 +1116,7 @@ static int bm_rw(struct drbd_conf *mdev, int rw, unsigned lazy_writeout_upper_id dev_info(DEV, "%s (%lu bits) marked out-of-sync by on disk bit-map.\n", ppsize(ppb, now << (BM_BLOCK_SHIFT-10)), now); + kref_put(&ctx->kref, &bm_aio_ctx_destroy); return err; } @@ -1091,7 +1126,7 @@ static int bm_rw(struct drbd_conf *mdev, int rw, unsigned lazy_writeout_upper_id */ int drbd_bm_read(struct drbd_conf *mdev) __must_hold(local) { - return bm_rw(mdev, READ, 0); + return bm_rw(mdev, READ, 0, 0); } /** @@ -1102,7 +1137,7 @@ int drbd_bm_read(struct drbd_conf *mdev) __must_hold(local) */ int drbd_bm_write(struct drbd_conf *mdev) __must_hold(local) { - return bm_rw(mdev, WRITE, 0); + return bm_rw(mdev, WRITE, 0, 0); } /** @@ -1112,7 +1147,23 @@ int drbd_bm_write(struct drbd_conf *mdev) __must_hold(local) */ int drbd_bm_write_lazy(struct drbd_conf *mdev, unsigned upper_idx) __must_hold(local) { - return bm_rw(mdev, WRITE, upper_idx); + return bm_rw(mdev, WRITE, BM_AIO_COPY_PAGES, upper_idx); +} + +/** + * drbd_bm_write_copy_pages() - Write the whole bitmap to its on disk location. + * @mdev: DRBD device. + * + * Will only write pages that have changed since last IO. + * In contrast to drbd_bm_write(), this will copy the bitmap pages + * to temporary writeout pages. It is intended to trigger a full write-out + * while still allowing the bitmap to change, for example if a resync or online + * verify is aborted due to a failed peer disk, while local IO continues, or + * pending resync acks are still being processed. + */ +int drbd_bm_write_copy_pages(struct drbd_conf *mdev) __must_hold(local) +{ + return bm_rw(mdev, WRITE, BM_AIO_COPY_PAGES, 0); } @@ -1130,28 +1181,45 @@ int drbd_bm_write_lazy(struct drbd_conf *mdev, unsigned upper_idx) __must_hold(l */ int drbd_bm_write_page(struct drbd_conf *mdev, unsigned int idx) __must_hold(local) { - struct bm_aio_ctx ctx = { + struct bm_aio_ctx *ctx; + int err; + + if (bm_test_page_unchanged(mdev->bitmap->bm_pages[idx])) { + dynamic_dev_dbg(DEV, "skipped bm page write for idx %u\n", idx); + return 0; + } + + ctx = kmalloc(sizeof(struct bm_aio_ctx), GFP_NOIO); + if (!ctx) + return -ENOMEM; + + *ctx = (struct bm_aio_ctx) { .mdev = mdev, .in_flight = ATOMIC_INIT(1), - .done = COMPLETION_INITIALIZER_ONSTACK(ctx.done), + .done = 0, .flags = BM_AIO_COPY_PAGES, + .error = 0, + .kref = { ATOMIC_INIT(2) }, }; - if (bm_test_page_unchanged(mdev->bitmap->bm_pages[idx])) { - dynamic_dev_dbg(DEV, "skipped bm page write for idx %u\n", idx); - return 0; + if (!get_ldev_if_state(mdev, D_ATTACHING)) { /* put is in bm_aio_ctx_destroy() */ + dev_err(DEV, "ASSERT FAILED: get_ldev_if_state() == 1 in drbd_bm_write_page()\n"); + kfree(ctx); + return -ENODEV; } - bm_page_io_async(&ctx, idx, WRITE_SYNC); - wait_for_completion(&ctx.done); + bm_page_io_async(ctx, idx, WRITE_SYNC); + wait_until_done_or_disk_failure(mdev, mdev->ldev, &ctx->done); - if (ctx.error) + if (ctx->error) drbd_chk_io_error(mdev, 1, true); /* that should force detach, so the in memory bitmap will be * gone in a moment as well. */ mdev->bm_writ_cnt++; - return ctx.error; + err = atomic_read(&ctx->in_flight) ? -EIO : ctx->error; + kref_put(&ctx->kref, &bm_aio_ctx_destroy); + return err; } /* NOTE diff --git a/drivers/block/drbd/drbd_int.h b/drivers/block/drbd/drbd_int.h index 8d680562ba7..02f013a073a 100644 --- a/drivers/block/drbd/drbd_int.h +++ b/drivers/block/drbd/drbd_int.h @@ -712,7 +712,6 @@ struct drbd_request { struct list_head tl_requests; /* ring list in the transfer log */ struct bio *master_bio; /* master bio pointer */ unsigned long rq_state; /* see comments above _req_mod() */ - int seq_num; unsigned long start_time; }; @@ -851,6 +850,7 @@ enum { NEW_CUR_UUID, /* Create new current UUID when thawing IO */ AL_SUSPENDED, /* Activity logging is currently suspended. */ AHEAD_TO_SYNC_SOURCE, /* Ahead -> SyncSource queued */ + STATE_SENT, /* Do not change state/UUIDs while this is set */ }; struct drbd_bitmap; /* opaque for drbd_conf */ @@ -862,31 +862,30 @@ enum bm_flag { BM_P_VMALLOCED = 0x10000, /* internal use only, will be masked out */ /* currently locked for bulk operation */ - BM_LOCKED_MASK = 0x7, + BM_LOCKED_MASK = 0xf, /* in detail, that is: */ BM_DONT_CLEAR = 0x1, BM_DONT_SET = 0x2, BM_DONT_TEST = 0x4, + /* so we can mark it locked for bulk operation, + * and still allow all non-bulk operations */ + BM_IS_LOCKED = 0x8, + /* (test bit, count bit) allowed (common case) */ - BM_LOCKED_TEST_ALLOWED = 0x3, + BM_LOCKED_TEST_ALLOWED = BM_DONT_CLEAR | BM_DONT_SET | BM_IS_LOCKED, /* testing bits, as well as setting new bits allowed, but clearing bits * would be unexpected. Used during bitmap receive. Setting new bits * requires sending of "out-of-sync" information, though. */ - BM_LOCKED_SET_ALLOWED = 0x1, + BM_LOCKED_SET_ALLOWED = BM_DONT_CLEAR | BM_IS_LOCKED, - /* clear is not expected while bitmap is locked for bulk operation */ + /* for drbd_bm_write_copy_pages, everything is allowed, + * only concurrent bulk operations are locked out. */ + BM_LOCKED_CHANGE_ALLOWED = BM_IS_LOCKED, }; - -/* TODO sort members for performance - * MAYBE group them further */ - -/* THINK maybe we actually want to use the default "event/%s" worker threads - * or similar in linux 2.6, which uses per cpu data and threads. - */ struct drbd_work_queue { struct list_head q; struct semaphore s; /* producers up it, worker down()s it */ @@ -938,8 +937,7 @@ struct drbd_backing_dev { }; struct drbd_md_io { - struct drbd_conf *mdev; - struct completion event; + unsigned int done; int error; }; @@ -1022,6 +1020,7 @@ struct drbd_conf { struct drbd_tl_epoch *newest_tle; struct drbd_tl_epoch *oldest_tle; struct list_head out_of_sequence_requests; + struct list_head barrier_acked_requests; struct hlist_head *tl_hash; unsigned int tl_hash_s; @@ -1056,6 +1055,8 @@ struct drbd_conf { struct crypto_hash *csums_tfm; struct crypto_hash *verify_tfm; + unsigned long last_reattach_jif; + unsigned long last_reconnect_jif; struct drbd_thread receiver; struct drbd_thread worker; struct drbd_thread asender; @@ -1094,7 +1095,8 @@ struct drbd_conf { wait_queue_head_t ee_wait; struct page *md_io_page; /* one page buffer for md_io */ struct page *md_io_tmpp; /* for logical_block_size != 512 */ - struct mutex md_io_mutex; /* protects the md_io_buffer */ + struct drbd_md_io md_io; + atomic_t md_io_in_use; /* protects the md_io, md_io_page and md_io_tmpp */ spinlock_t al_lock; wait_queue_head_t al_wait; struct lru_cache *act_log; /* activity log */ @@ -1228,8 +1230,8 @@ extern int drbd_send_uuids(struct drbd_conf *mdev); extern int drbd_send_uuids_skip_initial_sync(struct drbd_conf *mdev); extern int drbd_gen_and_send_sync_uuid(struct drbd_conf *mdev); extern int drbd_send_sizes(struct drbd_conf *mdev, int trigger_reply, enum dds_flags flags); -extern int _drbd_send_state(struct drbd_conf *mdev); -extern int drbd_send_state(struct drbd_conf *mdev); +extern int drbd_send_state(struct drbd_conf *mdev, union drbd_state s); +extern int drbd_send_current_state(struct drbd_conf *mdev); extern int _drbd_send_cmd(struct drbd_conf *mdev, struct socket *sock, enum drbd_packets cmd, struct p_header80 *h, size_t size, unsigned msg_flags); @@ -1461,6 +1463,7 @@ extern int drbd_bm_e_weight(struct drbd_conf *mdev, unsigned long enr); extern int drbd_bm_write_page(struct drbd_conf *mdev, unsigned int idx) __must_hold(local); extern int drbd_bm_read(struct drbd_conf *mdev) __must_hold(local); extern int drbd_bm_write(struct drbd_conf *mdev) __must_hold(local); +extern int drbd_bm_write_copy_pages(struct drbd_conf *mdev) __must_hold(local); extern unsigned long drbd_bm_ALe_set_all(struct drbd_conf *mdev, unsigned long al_enr); extern size_t drbd_bm_words(struct drbd_conf *mdev); @@ -1493,11 +1496,38 @@ extern struct kmem_cache *drbd_al_ext_cache; /* activity log extents */ extern mempool_t *drbd_request_mempool; extern mempool_t *drbd_ee_mempool; -extern struct page *drbd_pp_pool; /* drbd's page pool */ +/* drbd's page pool, used to buffer data received from the peer, + * or data requested by the peer. + * + * This does not have an emergency reserve. + * + * When allocating from this pool, it first takes pages from the pool. + * Only if the pool is depleted will try to allocate from the system. + * + * The assumption is that pages taken from this pool will be processed, + * and given back, "quickly", and then can be recycled, so we can avoid + * frequent calls to alloc_page(), and still will be able to make progress even + * under memory pressure. + */ +extern struct page *drbd_pp_pool; extern spinlock_t drbd_pp_lock; extern int drbd_pp_vacant; extern wait_queue_head_t drbd_pp_wait; +/* We also need a standard (emergency-reserve backed) page pool + * for meta data IO (activity log, bitmap). + * We can keep it global, as long as it is used as "N pages at a time". + * 128 should be plenty, currently we probably can get away with as few as 1. + */ +#define DRBD_MIN_POOL_PAGES 128 +extern mempool_t *drbd_md_io_page_pool; + +/* We also need to make sure we get a bio + * when we need it for housekeeping purposes */ +extern struct bio_set *drbd_md_io_bio_set; +/* to allocate from that set */ +extern struct bio *bio_alloc_drbd(gfp_t gfp_mask); + extern rwlock_t global_state_lock; extern struct drbd_conf *drbd_new_device(unsigned int minor); @@ -1536,8 +1566,12 @@ extern void resume_next_sg(struct drbd_conf *mdev); extern void suspend_other_sg(struct drbd_conf *mdev); extern int drbd_resync_finished(struct drbd_conf *mdev); /* maybe rather drbd_main.c ? */ +extern void *drbd_md_get_buffer(struct drbd_conf *mdev); +extern void drbd_md_put_buffer(struct drbd_conf *mdev); extern int drbd_md_sync_page_io(struct drbd_conf *mdev, - struct drbd_backing_dev *bdev, sector_t sector, int rw); + struct drbd_backing_dev *bdev, sector_t sector, int rw); +extern void wait_until_done_or_disk_failure(struct drbd_conf *mdev, struct drbd_backing_dev *bdev, + unsigned int *done); extern void drbd_ov_oos_found(struct drbd_conf*, sector_t, int); extern void drbd_rs_controller_reset(struct drbd_conf *mdev); @@ -1754,19 +1788,6 @@ static inline struct page *page_chain_next(struct page *page) #define page_chain_for_each_safe(page, n) \ for (; page && ({ n = page_chain_next(page); 1; }); page = n) -static inline int drbd_bio_has_active_page(struct bio *bio) -{ - struct bio_vec *bvec; - int i; - - __bio_for_each_segment(bvec, bio, i, 0) { - if (page_count(bvec->bv_page) > 1) - return 1; - } - - return 0; -} - static inline int drbd_ee_has_active_page(struct drbd_epoch_entry *e) { struct page *page = e->pages; @@ -1777,7 +1798,6 @@ static inline int drbd_ee_has_active_page(struct drbd_epoch_entry *e) return 0; } - static inline void drbd_state_lock(struct drbd_conf *mdev) { wait_event(mdev->misc_wait, @@ -2230,7 +2250,7 @@ static inline void drbd_get_syncer_progress(struct drbd_conf *mdev, * Note: currently we don't support such large bitmaps on 32bit * arch anyways, but no harm done to be prepared for it here. */ - unsigned int shift = mdev->rs_total >= (1ULL << 32) ? 16 : 10; + unsigned int shift = mdev->rs_total > UINT_MAX ? 16 : 10; unsigned long left = *bits_left >> shift; unsigned long total = 1UL + (mdev->rs_total >> shift); unsigned long tmp = 1000UL - left * 1000UL/total; @@ -2306,12 +2326,12 @@ static inline int drbd_state_is_stable(struct drbd_conf *mdev) case D_OUTDATED: case D_CONSISTENT: case D_UP_TO_DATE: + case D_FAILED: /* disk state is stable as well. */ break; /* no new io accepted during tansitional states */ case D_ATTACHING: - case D_FAILED: case D_NEGOTIATING: case D_UNKNOWN: case D_MASK: diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c index 211fc44f84b..920ede2829d 100644 --- a/drivers/block/drbd/drbd_main.c +++ b/drivers/block/drbd/drbd_main.c @@ -139,6 +139,8 @@ struct kmem_cache *drbd_bm_ext_cache; /* bitmap extents */ struct kmem_cache *drbd_al_ext_cache; /* activity log extents */ mempool_t *drbd_request_mempool; mempool_t *drbd_ee_mempool; +mempool_t *drbd_md_io_page_pool; +struct bio_set *drbd_md_io_bio_set; /* I do not use a standard mempool, because: 1) I want to hand out the pre-allocated objects first. @@ -159,7 +161,24 @@ static const struct block_device_operations drbd_ops = { .release = drbd_release, }; -#define ARRY_SIZE(A) (sizeof(A)/sizeof(A[0])) +static void bio_destructor_drbd(struct bio *bio) +{ + bio_free(bio, drbd_md_io_bio_set); +} + +struct bio *bio_alloc_drbd(gfp_t gfp_mask) +{ + struct bio *bio; + + if (!drbd_md_io_bio_set) + return bio_alloc(gfp_mask, 1); + + bio = bio_alloc_bioset(gfp_mask, 1, drbd_md_io_bio_set); + if (!bio) + return NULL; + bio->bi_destructor = bio_destructor_drbd; + return bio; +} #ifdef __CHECKER__ /* When checking with sparse, and this is an inline function, sparse will @@ -208,6 +227,7 @@ static int tl_init(struct drbd_conf *mdev) mdev->oldest_tle = b; mdev->newest_tle = b; INIT_LIST_HEAD(&mdev->out_of_sequence_requests); + INIT_LIST_HEAD(&mdev->barrier_acked_requests); mdev->tl_hash = NULL; mdev->tl_hash_s = 0; @@ -246,9 +266,7 @@ void _tl_add_barrier(struct drbd_conf *mdev, struct drbd_tl_epoch *new) new->n_writes = 0; newest_before = mdev->newest_tle; - /* never send a barrier number == 0, because that is special-cased - * when using TCQ for our write ordering code */ - new->br_number = (newest_before->br_number+1) ?: 1; + new->br_number = newest_before->br_number+1; if (mdev->newest_tle != new) { mdev->newest_tle->next = new; mdev->newest_tle = new; @@ -311,7 +329,7 @@ void tl_release(struct drbd_conf *mdev, unsigned int barrier_nr, These have been list_move'd to the out_of_sequence_requests list in _req_mod(, barrier_acked) above. */ - list_del_init(&b->requests); + list_splice_init(&b->requests, &mdev->barrier_acked_requests); nob = b->next; if (test_and_clear_bit(CREATE_BARRIER, &mdev->flags)) { @@ -411,6 +429,23 @@ static void _tl_restart(struct drbd_conf *mdev, enum drbd_req_event what) b = tmp; list_splice(&carry_reads, &b->requests); } + + /* Actions operating on the disk state, also want to work on + requests that got barrier acked. */ + switch (what) { + case fail_frozen_disk_io: + case restart_frozen_disk_io: + list_for_each_safe(le, tle, &mdev->barrier_acked_requests) { + req = list_entry(le, struct drbd_request, tl_requests); + _req_mod(req, what); + } + + case connection_lost_while_pending: + case resend: + break; + default: + dev_err(DEV, "what = %d in _tl_restart()\n", what); + } } @@ -458,6 +493,38 @@ void tl_restart(struct drbd_conf *mdev, enum drbd_req_event what) } /** + * tl_abort_disk_io() - Abort disk I/O for all requests for a certain mdev in the TL + * @mdev: DRBD device. + */ +void tl_abort_disk_io(struct drbd_conf *mdev) +{ + struct drbd_tl_epoch *b; + struct list_head *le, *tle; + struct drbd_request *req; + + spin_lock_irq(&mdev->req_lock); + b = mdev->oldest_tle; + while (b) { + list_for_each_safe(le, tle, &b->requests) { + req = list_entry(le, struct drbd_request, tl_requests); + if (!(req->rq_state & RQ_LOCAL_PENDING)) + continue; + _req_mod(req, abort_disk_io); + } + b = b->next; + } + + list_for_each_safe(le, tle, &mdev->barrier_acked_requests) { + req = list_entry(le, struct drbd_request, tl_requests); + if (!(req->rq_state & RQ_LOCAL_PENDING)) + continue; + _req_mod(req, abort_disk_io); + } + + spin_unlock_irq(&mdev->req_lock); +} + +/** * cl_wide_st_chg() - true if the state change is a cluster wide one * @mdev: DRBD device. * @os: old (current) state. @@ -470,7 +537,7 @@ static int cl_wide_st_chg(struct drbd_conf *mdev, ((os.role != R_PRIMARY && ns.role == R_PRIMARY) || (os.conn != C_STARTING_SYNC_T && ns.conn == C_STARTING_SYNC_T) || (os.conn != C_STARTING_SYNC_S && ns.conn == C_STARTING_SYNC_S) || - (os.disk != D_DISKLESS && ns.disk == D_DISKLESS))) || + (os.disk != D_FAILED && ns.disk == D_FAILED))) || (os.conn >= C_CONNECTED && ns.conn == C_DISCONNECTING) || (os.conn == C_CONNECTED && ns.conn == C_VERIFY_S); } @@ -509,8 +576,16 @@ static enum drbd_state_rv is_valid_state(struct drbd_conf *, union drbd_state); static enum drbd_state_rv is_valid_state_transition(struct drbd_conf *, union drbd_state, union drbd_state); +enum sanitize_state_warnings { + NO_WARNING, + ABORTED_ONLINE_VERIFY, + ABORTED_RESYNC, + CONNECTION_LOST_NEGOTIATING, + IMPLICITLY_UPGRADED_DISK, + IMPLICITLY_UPGRADED_PDSK, +}; static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state os, - union drbd_state ns, const char **warn_sync_abort); + union drbd_state ns, enum sanitize_state_warnings *warn); int drbd_send_state_req(struct drbd_conf *, union drbd_state, union drbd_state); @@ -785,6 +860,13 @@ is_valid_state_transition(struct drbd_conf *mdev, union drbd_state ns, if (ns.conn == os.conn && ns.conn == C_WF_REPORT_PARAMS) rv = SS_IN_TRANSIENT_STATE; + /* While establishing a connection only allow cstate to change. + Delay/refuse role changes, detach attach etc... */ + if (test_bit(STATE_SENT, &mdev->flags) && + !(os.conn == C_WF_REPORT_PARAMS || + (ns.conn == C_WF_REPORT_PARAMS && os.conn == C_WF_CONNECTION))) + rv = SS_IN_TRANSIENT_STATE; + if ((ns.conn == C_VERIFY_S || ns.conn == C_VERIFY_T) && os.conn < C_CONNECTED) rv = SS_NEED_CONNECTION; @@ -803,6 +885,21 @@ is_valid_state_transition(struct drbd_conf *mdev, union drbd_state ns, return rv; } +static void print_sanitize_warnings(struct drbd_conf *mdev, enum sanitize_state_warnings warn) +{ + static const char *msg_table[] = { + [NO_WARNING] = "", + [ABORTED_ONLINE_VERIFY] = "Online-verify aborted.", + [ABORTED_RESYNC] = "Resync aborted.", + [CONNECTION_LOST_NEGOTIATING] = "Connection lost while negotiating, no data!", + [IMPLICITLY_UPGRADED_DISK] = "Implicitly upgraded disk", + [IMPLICITLY_UPGRADED_PDSK] = "Implicitly upgraded pdsk", + }; + + if (warn != NO_WARNING) + dev_warn(DEV, "%s\n", msg_table[warn]); +} + /** * sanitize_state() - Resolves implicitly necessary additional changes to a state transition * @mdev: DRBD device. @@ -814,11 +911,14 @@ is_valid_state_transition(struct drbd_conf *mdev, union drbd_state ns, * to D_UNKNOWN. This rule and many more along those lines are in this function. */ static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state os, - union drbd_state ns, const char **warn_sync_abort) + union drbd_state ns, enum sanitize_state_warnings *warn) { enum drbd_fencing_p fp; enum drbd_disk_state disk_min, disk_max, pdsk_min, pdsk_max; + if (warn) + *warn = NO_WARNING; + fp = FP_DONT_CARE; if (get_ldev(mdev)) { fp = mdev->ldev->dc.fencing; @@ -833,18 +933,13 @@ static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state /* After a network error (+C_TEAR_DOWN) only C_UNCONNECTED or C_DISCONNECTING can follow. * If you try to go into some Sync* state, that shall fail (elsewhere). */ if (os.conn >= C_TIMEOUT && os.conn <= C_TEAR_DOWN && - ns.conn != C_UNCONNECTED && ns.conn != C_DISCONNECTING && ns.conn <= C_TEAR_DOWN) + ns.conn != C_UNCONNECTED && ns.conn != C_DISCONNECTING && ns.conn <= C_CONNECTED) ns.conn = os.conn; /* we cannot fail (again) if we already detached */ if (ns.disk == D_FAILED && os.disk == D_DISKLESS) ns.disk = D_DISKLESS; - /* if we are only D_ATTACHING yet, - * we can (and should) go directly to D_DISKLESS. */ - if (ns.disk == D_FAILED && os.disk == D_ATTACHING) - ns.disk = D_DISKLESS; - /* After C_DISCONNECTING only C_STANDALONE may follow */ if (os.conn == C_DISCONNECTING && ns.conn != C_STANDALONE) ns.conn = os.conn; @@ -863,10 +958,9 @@ static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state /* Abort resync if a disk fails/detaches */ if (os.conn > C_CONNECTED && ns.conn > C_CONNECTED && (ns.disk <= D_FAILED || ns.pdsk <= D_FAILED)) { - if (warn_sync_abort) - *warn_sync_abort = - os.conn == C_VERIFY_S || os.conn == C_VERIFY_T ? - "Online-verify" : "Resync"; + if (warn) + *warn = os.conn == C_VERIFY_S || os.conn == C_VERIFY_T ? + ABORTED_ONLINE_VERIFY : ABORTED_RESYNC; ns.conn = C_CONNECTED; } @@ -877,7 +971,8 @@ static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state ns.disk = mdev->new_state_tmp.disk; ns.pdsk = mdev->new_state_tmp.pdsk; } else { - dev_alert(DEV, "Connection lost while negotiating, no data!\n"); + if (warn) + *warn = CONNECTION_LOST_NEGOTIATING; ns.disk = D_DISKLESS; ns.pdsk = D_UNKNOWN; } @@ -959,16 +1054,16 @@ static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state ns.disk = disk_max; if (ns.disk < disk_min) { - dev_warn(DEV, "Implicitly set disk from %s to %s\n", - drbd_disk_str(ns.disk), drbd_disk_str(disk_min)); + if (warn) + *warn = IMPLICITLY_UPGRADED_DISK; ns.disk = disk_min; } if (ns.pdsk > pdsk_max) ns.pdsk = pdsk_max; if (ns.pdsk < pdsk_min) { - dev_warn(DEV, "Implicitly set pdsk from %s to %s\n", - drbd_disk_str(ns.pdsk), drbd_disk_str(pdsk_min)); + if (warn) + *warn = IMPLICITLY_UPGRADED_PDSK; ns.pdsk = pdsk_min; } @@ -1045,12 +1140,12 @@ __drbd_set_state(struct drbd_conf *mdev, union drbd_state ns, { union drbd_state os; enum drbd_state_rv rv = SS_SUCCESS; - const char *warn_sync_abort = NULL; + enum sanitize_state_warnings ssw; struct after_state_chg_work *ascw; os = mdev->state; - ns = sanitize_state(mdev, os, ns, &warn_sync_abort); + ns = sanitize_state(mdev, os, ns, &ssw); if (ns.i == os.i) return SS_NOTHING_TO_DO; @@ -1076,8 +1171,7 @@ __drbd_set_state(struct drbd_conf *mdev, union drbd_state ns, return rv; } - if (warn_sync_abort) - dev_warn(DEV, "%s aborted.\n", warn_sync_abort); + print_sanitize_warnings(mdev, ssw); { char *pbp, pb[300]; @@ -1243,7 +1337,7 @@ __drbd_set_state(struct drbd_conf *mdev, union drbd_state ns, drbd_thread_stop_nowait(&mdev->receiver); /* Upon network failure, we need to restart the receiver. */ - if (os.conn > C_TEAR_DOWN && + if (os.conn > C_WF_CONNECTION && ns.conn <= C_TEAR_DOWN && ns.conn >= C_TIMEOUT) drbd_thread_restart_nowait(&mdev->receiver); @@ -1251,6 +1345,15 @@ __drbd_set_state(struct drbd_conf *mdev, union drbd_state ns, if (os.conn < C_CONNECTED && ns.conn >= C_CONNECTED) drbd_resume_al(mdev); + /* remember last connect and attach times so request_timer_fn() won't + * kill newly established sessions while we are still trying to thaw + * previously frozen IO */ + if (os.conn != C_WF_REPORT_PARAMS && ns.conn == C_WF_REPORT_PARAMS) + mdev->last_reconnect_jif = jiffies; + if ((os.disk == D_ATTACHING || os.disk == D_NEGOTIATING) && + ns.disk > D_NEGOTIATING) + mdev->last_reattach_jif = jiffies; + ascw = kmalloc(sizeof(*ascw), GFP_ATOMIC); if (ascw) { ascw->os = os; @@ -1354,12 +1457,16 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, /* Here we have the actions that are performed after a state change. This function might sleep */ + if (os.disk <= D_NEGOTIATING && ns.disk > D_NEGOTIATING) + mod_timer(&mdev->request_timer, jiffies + HZ); + nsm.i = -1; if (ns.susp_nod) { if (os.conn < C_CONNECTED && ns.conn >= C_CONNECTED) what = resend; - if (os.disk == D_ATTACHING && ns.disk > D_ATTACHING) + if ((os.disk == D_ATTACHING || os.disk == D_NEGOTIATING) && + ns.disk > D_NEGOTIATING) what = restart_frozen_disk_io; if (what != nothing) @@ -1408,7 +1515,7 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, /* Do not change the order of the if above and the two below... */ if (os.pdsk == D_DISKLESS && ns.pdsk > D_DISKLESS) { /* attach on the peer */ drbd_send_uuids(mdev); - drbd_send_state(mdev); + drbd_send_state(mdev, ns); } /* No point in queuing send_bitmap if we don't have a connection * anymore, so check also the _current_ state, not only the new state @@ -1441,11 +1548,11 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, } if (ns.pdsk < D_INCONSISTENT && get_ldev(mdev)) { - if (ns.peer == R_PRIMARY && mdev->ldev->md.uuid[UI_BITMAP] == 0) { + if (os.peer == R_SECONDARY && ns.peer == R_PRIMARY && + mdev->ldev->md.uuid[UI_BITMAP] == 0 && ns.disk >= D_UP_TO_DATE) { drbd_uuid_new_current(mdev); drbd_send_uuids(mdev); } - /* D_DISKLESS Peer becomes secondary */ if (os.peer == R_PRIMARY && ns.peer == R_SECONDARY) /* We may still be Primary ourselves. @@ -1473,14 +1580,14 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, os.disk == D_ATTACHING && ns.disk == D_NEGOTIATING) { drbd_send_sizes(mdev, 0, 0); /* to start sync... */ drbd_send_uuids(mdev); - drbd_send_state(mdev); + drbd_send_state(mdev, ns); } /* We want to pause/continue resync, tell peer. */ if (ns.conn >= C_CONNECTED && ((os.aftr_isp != ns.aftr_isp) || (os.user_isp != ns.user_isp))) - drbd_send_state(mdev); + drbd_send_state(mdev, ns); /* In case one of the isp bits got set, suspend other devices. */ if ((!os.aftr_isp && !os.peer_isp && !os.user_isp) && @@ -1490,10 +1597,10 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, /* Make sure the peer gets informed about eventual state changes (ISP bits) while we were in WFReportParams. */ if (os.conn == C_WF_REPORT_PARAMS && ns.conn >= C_CONNECTED) - drbd_send_state(mdev); + drbd_send_state(mdev, ns); if (os.conn != C_AHEAD && ns.conn == C_AHEAD) - drbd_send_state(mdev); + drbd_send_state(mdev, ns); /* We are in the progress to start a full sync... */ if ((os.conn != C_STARTING_SYNC_T && ns.conn == C_STARTING_SYNC_T) || @@ -1513,33 +1620,38 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, /* first half of local IO error, failure to attach, * or administrative detach */ if (os.disk != D_FAILED && ns.disk == D_FAILED) { - enum drbd_io_error_p eh; - int was_io_error; + enum drbd_io_error_p eh = EP_PASS_ON; + int was_io_error = 0; /* corresponding get_ldev was in __drbd_set_state, to serialize - * our cleanup here with the transition to D_DISKLESS, - * so it is safe to dreference ldev here. */ - eh = mdev->ldev->dc.on_io_error; - was_io_error = test_and_clear_bit(WAS_IO_ERROR, &mdev->flags); - - /* current state still has to be D_FAILED, - * there is only one way out: to D_DISKLESS, - * and that may only happen after our put_ldev below. */ - if (mdev->state.disk != D_FAILED) - dev_err(DEV, - "ASSERT FAILED: disk is %s during detach\n", - drbd_disk_str(mdev->state.disk)); - - if (drbd_send_state(mdev)) - dev_warn(DEV, "Notified peer that I am detaching my disk\n"); - else - dev_err(DEV, "Sending state for detaching disk failed\n"); - - drbd_rs_cancel_all(mdev); - - /* In case we want to get something to stable storage still, - * this may be the last chance. - * Following put_ldev may transition to D_DISKLESS. */ - drbd_md_sync(mdev); + * our cleanup here with the transition to D_DISKLESS. + * But is is still not save to dreference ldev here, since + * we might come from an failed Attach before ldev was set. */ + if (mdev->ldev) { + eh = mdev->ldev->dc.on_io_error; + was_io_error = test_and_clear_bit(WAS_IO_ERROR, &mdev->flags); + + /* Immediately allow completion of all application IO, that waits + for completion from the local disk. */ + tl_abort_disk_io(mdev); + + /* current state still has to be D_FAILED, + * there is only one way out: to D_DISKLESS, + * and that may only happen after our put_ldev below. */ + if (mdev->state.disk != D_FAILED) + dev_err(DEV, + "ASSERT FAILED: disk is %s during detach\n", + drbd_disk_str(mdev->state.disk)); + + if (ns.conn >= C_CONNECTED) + drbd_send_state(mdev, ns); + + drbd_rs_cancel_all(mdev); + + /* In case we want to get something to stable storage still, + * this may be the last chance. + * Following put_ldev may transition to D_DISKLESS. */ + drbd_md_sync(mdev); + } put_ldev(mdev); if (was_io_error && eh == EP_CALL_HELPER) @@ -1561,16 +1673,17 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, mdev->rs_failed = 0; atomic_set(&mdev->rs_pending_cnt, 0); - if (drbd_send_state(mdev)) - dev_warn(DEV, "Notified peer that I'm now diskless.\n"); + if (ns.conn >= C_CONNECTED) + drbd_send_state(mdev, ns); + /* corresponding get_ldev in __drbd_set_state * this may finally trigger drbd_ldev_destroy. */ put_ldev(mdev); } /* Notify peer that I had a local IO error, and did not detached.. */ - if (os.disk == D_UP_TO_DATE && ns.disk == D_INCONSISTENT) - drbd_send_state(mdev); + if (os.disk == D_UP_TO_DATE && ns.disk == D_INCONSISTENT && ns.conn >= C_CONNECTED) + drbd_send_state(mdev, ns); /* Disks got bigger while they were detached */ if (ns.disk > D_NEGOTIATING && ns.pdsk > D_NEGOTIATING && @@ -1588,7 +1701,13 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, /* sync target done with resync. Explicitly notify peer, even though * it should (at least for non-empty resyncs) already know itself. */ if (os.disk < D_UP_TO_DATE && os.conn >= C_SYNC_SOURCE && ns.conn == C_CONNECTED) - drbd_send_state(mdev); + drbd_send_state(mdev, ns); + + /* Wake up role changes, that were delayed because of connection establishing */ + if (os.conn == C_WF_REPORT_PARAMS && ns.conn != C_WF_REPORT_PARAMS) { + clear_bit(STATE_SENT, &mdev->flags); + wake_up(&mdev->state_wait); + } /* This triggers bitmap writeout of potentially still unwritten pages * if the resync finished cleanly, or aborted because of peer disk @@ -1598,8 +1717,8 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os, * No harm done if some bits change during this phase. */ if (os.conn > C_CONNECTED && ns.conn <= C_CONNECTED && get_ldev(mdev)) { - drbd_queue_bitmap_io(mdev, &drbd_bm_write, NULL, - "write from resync_finished", BM_LOCKED_SET_ALLOWED); + drbd_queue_bitmap_io(mdev, &drbd_bm_write_copy_pages, NULL, + "write from resync_finished", BM_LOCKED_CHANGE_ALLOWED); put_ldev(mdev); } @@ -2057,7 +2176,11 @@ int drbd_gen_and_send_sync_uuid(struct drbd_conf *mdev) D_ASSERT(mdev->state.disk == D_UP_TO_DATE); - uuid = mdev->ldev->md.uuid[UI_BITMAP] + UUID_NEW_BM_OFFSET; + uuid = mdev->ldev->md.uuid[UI_BITMAP]; + if (uuid && uuid != UUID_JUST_CREATED) + uuid = uuid + UUID_NEW_BM_OFFSET; + else + get_random_bytes(&uuid, sizeof(u64)); drbd_uuid_set(mdev, UI_BITMAP, uuid); drbd_print_uuids(mdev, "updated sync UUID"); drbd_md_sync(mdev); @@ -2089,6 +2212,10 @@ int drbd_send_sizes(struct drbd_conf *mdev, int trigger_reply, enum dds_flags fl max_bio_size = DRBD_MAX_BIO_SIZE; /* ... multiple BIOs per peer_request */ } + /* Never allow old drbd (up to 8.3.7) to see more than 32KiB */ + if (mdev->agreed_pro_version <= 94) + max_bio_size = min_t(int, max_bio_size, DRBD_MAX_SIZE_H80_PACKET); + p.d_size = cpu_to_be64(d_size); p.u_size = cpu_to_be64(u_size); p.c_size = cpu_to_be64(trigger_reply ? 0 : drbd_get_capacity(mdev->this_bdev)); @@ -2102,10 +2229,10 @@ int drbd_send_sizes(struct drbd_conf *mdev, int trigger_reply, enum dds_flags fl } /** - * drbd_send_state() - Sends the drbd state to the peer + * drbd_send_current_state() - Sends the drbd state to the peer * @mdev: DRBD device. */ -int drbd_send_state(struct drbd_conf *mdev) +int drbd_send_current_state(struct drbd_conf *mdev) { struct socket *sock; struct p_state p; @@ -2131,6 +2258,37 @@ int drbd_send_state(struct drbd_conf *mdev) return ok; } +/** + * drbd_send_state() - After a state change, sends the new state to the peer + * @mdev: DRBD device. + * @state: the state to send, not necessarily the current state. + * + * Each state change queues an "after_state_ch" work, which will eventually + * send the resulting new state to the peer. If more state changes happen + * between queuing and processing of the after_state_ch work, we still + * want to send each intermediary state in the order it occurred. + */ +int drbd_send_state(struct drbd_conf *mdev, union drbd_state state) +{ + struct socket *sock; + struct p_state p; + int ok = 0; + + mutex_lock(&mdev->data.mutex); + + p.state = cpu_to_be32(state.i); + sock = mdev->data.socket; + + if (likely(sock != NULL)) { + ok = _drbd_send_cmd(mdev, sock, P_STATE, + (struct p_header80 *)&p, sizeof(p), 0); + } + + mutex_unlock(&mdev->data.mutex); + + return ok; +} + int drbd_send_state_req(struct drbd_conf *mdev, union drbd_state mask, union drbd_state val) { @@ -2615,7 +2773,7 @@ static int _drbd_send_bio(struct drbd_conf *mdev, struct bio *bio) struct bio_vec *bvec; int i; /* hint all but last page with MSG_MORE */ - __bio_for_each_segment(bvec, bio, i, 0) { + bio_for_each_segment(bvec, bio, i) { if (!_drbd_no_send_page(mdev, bvec->bv_page, bvec->bv_offset, bvec->bv_len, i == bio->bi_vcnt -1 ? 0 : MSG_MORE)) @@ -2629,7 +2787,7 @@ static int _drbd_send_zc_bio(struct drbd_conf *mdev, struct bio *bio) struct bio_vec *bvec; int i; /* hint all but last page with MSG_MORE */ - __bio_for_each_segment(bvec, bio, i, 0) { + bio_for_each_segment(bvec, bio, i) { if (!_drbd_send_page(mdev, bvec->bv_page, bvec->bv_offset, bvec->bv_len, i == bio->bi_vcnt -1 ? 0 : MSG_MORE)) @@ -2695,8 +2853,7 @@ int drbd_send_dblock(struct drbd_conf *mdev, struct drbd_request *req) p.sector = cpu_to_be64(req->sector); p.block_id = (unsigned long)req; - p.seq_num = cpu_to_be32(req->seq_num = - atomic_add_return(1, &mdev->packet_seq)); + p.seq_num = cpu_to_be32(atomic_add_return(1, &mdev->packet_seq)); dp_flags = bio_flags_to_wire(mdev, req->master_bio->bi_rw); @@ -2987,8 +3144,8 @@ void drbd_init_set_defaults(struct drbd_conf *mdev) atomic_set(&mdev->rs_sect_in, 0); atomic_set(&mdev->rs_sect_ev, 0); atomic_set(&mdev->ap_in_flight, 0); + atomic_set(&mdev->md_io_in_use, 0); - mutex_init(&mdev->md_io_mutex); mutex_init(&mdev->data.mutex); mutex_init(&mdev->meta.mutex); sema_init(&mdev->data.work.s, 0); @@ -3126,6 +3283,10 @@ static void drbd_destroy_mempools(void) /* D_ASSERT(atomic_read(&drbd_pp_vacant)==0); */ + if (drbd_md_io_bio_set) + bioset_free(drbd_md_io_bio_set); + if (drbd_md_io_page_pool) + mempool_destroy(drbd_md_io_page_pool); if (drbd_ee_mempool) mempool_destroy(drbd_ee_mempool); if (drbd_request_mempool) @@ -3139,6 +3300,8 @@ static void drbd_destroy_mempools(void) if (drbd_al_ext_cache) kmem_cache_destroy(drbd_al_ext_cache); + drbd_md_io_bio_set = NULL; + drbd_md_io_page_pool = NULL; drbd_ee_mempool = NULL; drbd_request_mempool = NULL; drbd_ee_cache = NULL; @@ -3162,6 +3325,8 @@ static int drbd_create_mempools(void) drbd_bm_ext_cache = NULL; drbd_al_ext_cache = NULL; drbd_pp_pool = NULL; + drbd_md_io_page_pool = NULL; + drbd_md_io_bio_set = NULL; /* caches */ drbd_request_cache = kmem_cache_create( @@ -3185,6 +3350,16 @@ static int drbd_create_mempools(void) goto Enomem; /* mempools */ +#ifdef COMPAT_HAVE_BIOSET_CREATE + drbd_md_io_bio_set = bioset_create(DRBD_MIN_POOL_PAGES, 0); + if (drbd_md_io_bio_set == NULL) + goto Enomem; +#endif + + drbd_md_io_page_pool = mempool_create_page_pool(DRBD_MIN_POOL_PAGES, 0); + if (drbd_md_io_page_pool == NULL) + goto Enomem; + drbd_request_mempool = mempool_create(number, mempool_alloc_slab, mempool_free_slab, drbd_request_cache); if (drbd_request_mempool == NULL) @@ -3262,6 +3437,8 @@ static void drbd_delete_device(unsigned int minor) if (!mdev) return; + del_timer_sync(&mdev->request_timer); + /* paranoia asserts */ if (mdev->open_cnt != 0) dev_err(DEV, "open_cnt = %d in %s:%u", mdev->open_cnt, @@ -3666,8 +3843,10 @@ void drbd_md_sync(struct drbd_conf *mdev) if (!get_ldev_if_state(mdev, D_FAILED)) return; - mutex_lock(&mdev->md_io_mutex); - buffer = (struct meta_data_on_disk *)page_address(mdev->md_io_page); + buffer = drbd_md_get_buffer(mdev); + if (!buffer) + goto out; + memset(buffer, 0, 512); buffer->la_size = cpu_to_be64(drbd_get_capacity(mdev->this_bdev)); @@ -3698,7 +3877,8 @@ void drbd_md_sync(struct drbd_conf *mdev) * since we updated it on metadata. */ mdev->ldev->md.la_size_sect = drbd_get_capacity(mdev->this_bdev); - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); +out: put_ldev(mdev); } @@ -3718,8 +3898,9 @@ int drbd_md_read(struct drbd_conf *mdev, struct drbd_backing_dev *bdev) if (!get_ldev_if_state(mdev, D_ATTACHING)) return ERR_IO_MD_DISK; - mutex_lock(&mdev->md_io_mutex); - buffer = (struct meta_data_on_disk *)page_address(mdev->md_io_page); + buffer = drbd_md_get_buffer(mdev); + if (!buffer) + goto out; if (!drbd_md_sync_page_io(mdev, bdev, bdev->md.md_offset, READ)) { /* NOTE: can't do normal error processing here as this is @@ -3780,7 +3961,8 @@ int drbd_md_read(struct drbd_conf *mdev, struct drbd_backing_dev *bdev) mdev->sync_conf.al_extents = 127; err: - mutex_unlock(&mdev->md_io_mutex); + drbd_md_put_buffer(mdev); + out: put_ldev(mdev); return rv; @@ -4183,12 +4365,11 @@ const char *drbd_buildtag(void) static char buildtag[38] = "\0uilt-in"; if (buildtag[0] == 0) { -#ifdef CONFIG_MODULES - if (THIS_MODULE != NULL) - sprintf(buildtag, "srcversion: %-24s", THIS_MODULE->srcversion); - else +#ifdef MODULE + sprintf(buildtag, "srcversion: %-24s", THIS_MODULE->srcversion); +#else + buildtag[0] = 'b'; #endif - buildtag[0] = 'b'; } return buildtag; diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c index 946166e1395..6d4de6a72e8 100644 --- a/drivers/block/drbd/drbd_nl.c +++ b/drivers/block/drbd/drbd_nl.c @@ -289,7 +289,7 @@ static int _try_outdate_peer_async(void *data) */ spin_lock_irq(&mdev->req_lock); ns = mdev->state; - if (ns.conn < C_WF_REPORT_PARAMS) { + if (ns.conn < C_WF_REPORT_PARAMS && !test_bit(STATE_SENT, &mdev->flags)) { ns.pdsk = nps; _drbd_set_state(mdev, ns, CS_VERBOSE, NULL); } @@ -432,7 +432,7 @@ drbd_set_role(struct drbd_conf *mdev, enum drbd_role new_role, int force) /* if this was forced, we should consider sync */ if (forced) drbd_send_uuids(mdev); - drbd_send_state(mdev); + drbd_send_current_state(mdev); } drbd_md_sync(mdev); @@ -845,9 +845,10 @@ void drbd_reconsider_max_bio_size(struct drbd_conf *mdev) Because new from 8.3.8 onwards the peer can use multiple BIOs for a single peer_request */ if (mdev->state.conn >= C_CONNECTED) { - if (mdev->agreed_pro_version < 94) - peer = mdev->peer_max_bio_size; - else if (mdev->agreed_pro_version == 94) + if (mdev->agreed_pro_version < 94) { + peer = min_t(int, mdev->peer_max_bio_size, DRBD_MAX_SIZE_H80_PACKET); + /* Correct old drbd (up to 8.3.7) if it believes it can do more than 32KiB */ + } else if (mdev->agreed_pro_version == 94) peer = DRBD_MAX_SIZE_H80_PACKET; else /* drbd 8.3.8 onwards */ peer = DRBD_MAX_BIO_SIZE; @@ -1032,7 +1033,7 @@ static int drbd_nl_disk_conf(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp dev_err(DEV, "max capacity %llu smaller than disk size %llu\n", (unsigned long long) drbd_get_max_capacity(nbc), (unsigned long long) nbc->dc.disk_size); - retcode = ERR_DISK_TO_SMALL; + retcode = ERR_DISK_TOO_SMALL; goto fail; } @@ -1046,7 +1047,7 @@ static int drbd_nl_disk_conf(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp } if (drbd_get_capacity(nbc->md_bdev) < min_md_device_sectors) { - retcode = ERR_MD_DISK_TO_SMALL; + retcode = ERR_MD_DISK_TOO_SMALL; dev_warn(DEV, "refusing attach: md-device too small, " "at least %llu sectors needed for this meta-disk type\n", (unsigned long long) min_md_device_sectors); @@ -1057,7 +1058,7 @@ static int drbd_nl_disk_conf(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp * (we may currently be R_PRIMARY with no local disk...) */ if (drbd_get_max_capacity(nbc) < drbd_get_capacity(mdev->this_bdev)) { - retcode = ERR_DISK_TO_SMALL; + retcode = ERR_DISK_TOO_SMALL; goto fail; } @@ -1138,7 +1139,7 @@ static int drbd_nl_disk_conf(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp if (drbd_md_test_flag(nbc, MDF_CONSISTENT) && drbd_new_dev_size(mdev, nbc, 0) < nbc->md.la_size_sect) { dev_warn(DEV, "refusing to truncate a consistent device\n"); - retcode = ERR_DISK_TO_SMALL; + retcode = ERR_DISK_TOO_SMALL; goto force_diskless_dec; } @@ -1336,17 +1337,34 @@ static int drbd_nl_detach(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp, { enum drbd_ret_code retcode; int ret; + struct detach dt = {}; + + if (!detach_from_tags(mdev, nlp->tag_list, &dt)) { + reply->ret_code = ERR_MANDATORY_TAG; + goto out; + } + + if (dt.detach_force) { + drbd_force_state(mdev, NS(disk, D_FAILED)); + reply->ret_code = SS_SUCCESS; + goto out; + } + drbd_suspend_io(mdev); /* so no-one is stuck in drbd_al_begin_io */ + drbd_md_get_buffer(mdev); /* make sure there is no in-flight meta-data IO */ retcode = drbd_request_state(mdev, NS(disk, D_FAILED)); + drbd_md_put_buffer(mdev); /* D_FAILED will transition to DISKLESS. */ ret = wait_event_interruptible(mdev->misc_wait, mdev->state.disk != D_FAILED); drbd_resume_io(mdev); + if ((int)retcode == (int)SS_IS_DISKLESS) retcode = SS_NOTHING_TO_DO; if (ret) retcode = ERR_INTR; reply->ret_code = retcode; +out: return 0; } @@ -1711,7 +1729,7 @@ static int drbd_nl_resize(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp, if (rs.no_resync && mdev->agreed_pro_version < 93) { retcode = ERR_NEED_APV_93; - goto fail; + goto fail_ldev; } if (mdev->ldev->known_size != drbd_get_capacity(mdev->ldev->backing_bdev)) @@ -1738,6 +1756,10 @@ static int drbd_nl_resize(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp, fail: reply->ret_code = retcode; return 0; + + fail_ldev: + put_ldev(mdev); + goto fail; } static int drbd_nl_syncer_conf(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp, @@ -1941,6 +1963,7 @@ static int drbd_nl_invalidate(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nl /* If there is still bitmap IO pending, probably because of a previous * resync just being finished, wait for it before requesting a new resync. */ + drbd_suspend_io(mdev); wait_event(mdev->misc_wait, !test_bit(BITMAP_IO, &mdev->flags)); retcode = _drbd_request_state(mdev, NS(conn, C_STARTING_SYNC_T), CS_ORDERED); @@ -1959,6 +1982,7 @@ static int drbd_nl_invalidate(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nl retcode = drbd_request_state(mdev, NS(conn, C_STARTING_SYNC_T)); } + drbd_resume_io(mdev); reply->ret_code = retcode; return 0; @@ -1980,6 +2004,7 @@ static int drbd_nl_invalidate_peer(struct drbd_conf *mdev, struct drbd_nl_cfg_re /* If there is still bitmap IO pending, probably because of a previous * resync just being finished, wait for it before requesting a new resync. */ + drbd_suspend_io(mdev); wait_event(mdev->misc_wait, !test_bit(BITMAP_IO, &mdev->flags)); retcode = _drbd_request_state(mdev, NS(conn, C_STARTING_SYNC_S), CS_ORDERED); @@ -1998,6 +2023,7 @@ static int drbd_nl_invalidate_peer(struct drbd_conf *mdev, struct drbd_nl_cfg_re } else retcode = drbd_request_state(mdev, NS(conn, C_STARTING_SYNC_S)); } + drbd_resume_io(mdev); reply->ret_code = retcode; return 0; @@ -2170,11 +2196,13 @@ static int drbd_nl_start_ov(struct drbd_conf *mdev, struct drbd_nl_cfg_req *nlp, /* If there is still bitmap IO pending, e.g. previous resync or verify * just being finished, wait for it before requesting a new resync. */ + drbd_suspend_io(mdev); wait_event(mdev->misc_wait, !test_bit(BITMAP_IO, &mdev->flags)); /* w_make_ov_request expects position to be aligned */ mdev->ov_start_sector = args.start_sector & ~BM_SECT_PER_BIT; reply->ret_code = drbd_request_state(mdev,NS(conn,C_VERIFY_S)); + drbd_resume_io(mdev); return 0; } diff --git a/drivers/block/drbd/drbd_proc.c b/drivers/block/drbd/drbd_proc.c index 2959cdfb77f..869bada2ed0 100644 --- a/drivers/block/drbd/drbd_proc.c +++ b/drivers/block/drbd/drbd_proc.c @@ -52,7 +52,7 @@ void seq_printf_with_thousands_grouping(struct seq_file *seq, long v) if (unlikely(v >= 1000000)) { /* cool: > GiByte/s */ seq_printf(seq, "%ld,", v / 1000000); - v /= 1000000; + v %= 1000000; seq_printf(seq, "%03ld,%03ld", v/1000, v % 1000); } else if (likely(v >= 1000)) seq_printf(seq, "%ld,%03ld", v/1000, v % 1000); diff --git a/drivers/block/drbd/drbd_receiver.c b/drivers/block/drbd/drbd_receiver.c index 436f519bed1..ea4836e0ae9 100644 --- a/drivers/block/drbd/drbd_receiver.c +++ b/drivers/block/drbd/drbd_receiver.c @@ -466,6 +466,7 @@ static int drbd_accept(struct drbd_conf *mdev, const char **what, goto out; } (*newsock)->ops = sock->ops; + __module_get((*newsock)->ops->owner); out: return err; @@ -750,6 +751,7 @@ static int drbd_connect(struct drbd_conf *mdev) { struct socket *s, *sock, *msock; int try, h, ok; + enum drbd_state_rv rv; D_ASSERT(!mdev->data.socket); @@ -888,25 +890,32 @@ retry: } } - if (drbd_request_state(mdev, NS(conn, C_WF_REPORT_PARAMS)) < SS_SUCCESS) - return 0; - sock->sk->sk_sndtimeo = mdev->net_conf->timeout*HZ/10; sock->sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; atomic_set(&mdev->packet_seq, 0); mdev->peer_seq = 0; - drbd_thread_start(&mdev->asender); - if (drbd_send_protocol(mdev) == -1) return -1; + set_bit(STATE_SENT, &mdev->flags); drbd_send_sync_param(mdev, &mdev->sync_conf); drbd_send_sizes(mdev, 0, 0); drbd_send_uuids(mdev); - drbd_send_state(mdev); + drbd_send_current_state(mdev); clear_bit(USE_DEGR_WFC_T, &mdev->flags); clear_bit(RESIZE_PENDING, &mdev->flags); + + spin_lock_irq(&mdev->req_lock); + rv = _drbd_set_state(_NS(mdev, conn, C_WF_REPORT_PARAMS), CS_VERBOSE, NULL); + if (mdev->state.conn != C_WF_REPORT_PARAMS) + clear_bit(STATE_SENT, &mdev->flags); + spin_unlock_irq(&mdev->req_lock); + + if (rv < SS_SUCCESS) + return 0; + + drbd_thread_start(&mdev->asender); mod_timer(&mdev->request_timer, jiffies + HZ); /* just start it here. */ return 1; @@ -957,7 +966,7 @@ static void drbd_flush(struct drbd_conf *mdev) rv = blkdev_issue_flush(mdev->ldev->backing_bdev, GFP_KERNEL, NULL); if (rv) { - dev_err(DEV, "local disk flush failed with status %d\n", rv); + dev_info(DEV, "local disk flush failed with status %d\n", rv); /* would rather check on EOPNOTSUPP, but that is not reliable. * don't try again for ANY return value != 0 * if (rv == -EOPNOTSUPP) */ @@ -1001,13 +1010,14 @@ static enum finish_epoch drbd_may_finish_epoch(struct drbd_conf *mdev, if (epoch_size != 0 && atomic_read(&epoch->active) == 0 && - test_bit(DE_HAVE_BARRIER_NUMBER, &epoch->flags)) { + (test_bit(DE_HAVE_BARRIER_NUMBER, &epoch->flags) || ev & EV_CLEANUP)) { if (!(ev & EV_CLEANUP)) { spin_unlock(&mdev->epoch_lock); drbd_send_b_ack(mdev, epoch->barrier_nr, epoch_size); spin_lock(&mdev->epoch_lock); } - dec_unacked(mdev); + if (test_bit(DE_HAVE_BARRIER_NUMBER, &epoch->flags)) + dec_unacked(mdev); if (mdev->current_epoch != epoch) { next_epoch = list_entry(epoch->list.next, struct drbd_epoch, list); @@ -1096,7 +1106,11 @@ int drbd_submit_ee(struct drbd_conf *mdev, struct drbd_epoch_entry *e, /* In most cases, we will only need one bio. But in case the lower * level restrictions happen to be different at this offset on this * side than those of the sending peer, we may need to submit the - * request in more than one bio. */ + * request in more than one bio. + * + * Plain bio_alloc is good enough here, this is no DRBD internally + * generated bio, but a bio allocated on behalf of the peer. + */ next_bio: bio = bio_alloc(GFP_NOIO, nr_pages); if (!bio) { @@ -1583,6 +1597,24 @@ static int e_send_discard_ack(struct drbd_conf *mdev, struct drbd_work *w, int u return ok; } +static bool overlapping_resync_write(struct drbd_conf *mdev, struct drbd_epoch_entry *data_e) +{ + + struct drbd_epoch_entry *rs_e; + bool rv = 0; + + spin_lock_irq(&mdev->req_lock); + list_for_each_entry(rs_e, &mdev->sync_ee, w.list) { + if (overlaps(data_e->sector, data_e->size, rs_e->sector, rs_e->size)) { + rv = 1; + break; + } + } + spin_unlock_irq(&mdev->req_lock); + + return rv; +} + /* Called from receive_Data. * Synchronize packets on sock with packets on msock. * @@ -1826,6 +1858,9 @@ static int receive_Data(struct drbd_conf *mdev, enum drbd_packets cmd, unsigned list_add(&e->w.list, &mdev->active_ee); spin_unlock_irq(&mdev->req_lock); + if (mdev->state.conn == C_SYNC_TARGET) + wait_event(mdev->ee_wait, !overlapping_resync_write(mdev, e)); + switch (mdev->net_conf->wire_protocol) { case DRBD_PROT_C: inc_unacked(mdev); @@ -2420,7 +2455,7 @@ static int drbd_uuid_compare(struct drbd_conf *mdev, int *rule_nr) __must_hold(l mdev->p_uuid[UI_BITMAP] = mdev->p_uuid[UI_HISTORY_START]; mdev->p_uuid[UI_HISTORY_START] = mdev->p_uuid[UI_HISTORY_START + 1]; - dev_info(DEV, "Did not got last syncUUID packet, corrected:\n"); + dev_info(DEV, "Lost last syncUUID packet, corrected:\n"); drbd_uuid_dump(mdev, "peer", mdev->p_uuid, mdev->p_uuid[UI_SIZE], mdev->p_uuid[UI_FLAGS]); return -1; @@ -2806,10 +2841,10 @@ static int receive_SyncParam(struct drbd_conf *mdev, enum drbd_packets cmd, unsi if (apv >= 88) { if (apv == 88) { - if (data_size > SHARED_SECRET_MAX) { - dev_err(DEV, "verify-alg too long, " - "peer wants %u, accepting only %u byte\n", - data_size, SHARED_SECRET_MAX); + if (data_size > SHARED_SECRET_MAX || data_size == 0) { + dev_err(DEV, "verify-alg of wrong size, " + "peer wants %u, accepting only up to %u byte\n", + data_size, SHARED_SECRET_MAX); return false; } @@ -3168,9 +3203,20 @@ static int receive_state(struct drbd_conf *mdev, enum drbd_packets cmd, unsigned os = ns = mdev->state; spin_unlock_irq(&mdev->req_lock); - /* peer says his disk is uptodate, while we think it is inconsistent, - * and this happens while we think we have a sync going on. */ - if (os.pdsk == D_INCONSISTENT && real_peer_disk == D_UP_TO_DATE && + /* If some other part of the code (asender thread, timeout) + * already decided to close the connection again, + * we must not "re-establish" it here. */ + if (os.conn <= C_TEAR_DOWN) + return false; + + /* If this is the "end of sync" confirmation, usually the peer disk + * transitions from D_INCONSISTENT to D_UP_TO_DATE. For empty (0 bits + * set) resync started in PausedSyncT, or if the timing of pause-/ + * unpause-sync events has been "just right", the peer disk may + * transition from D_CONSISTENT to D_UP_TO_DATE as well. + */ + if ((os.pdsk == D_INCONSISTENT || os.pdsk == D_CONSISTENT) && + real_peer_disk == D_UP_TO_DATE && os.conn > C_CONNECTED && os.disk == D_UP_TO_DATE) { /* If we are (becoming) SyncSource, but peer is still in sync * preparation, ignore its uptodate-ness to avoid flapping, it @@ -3288,7 +3334,7 @@ static int receive_state(struct drbd_conf *mdev, enum drbd_packets cmd, unsigned /* Nowadays only used when forcing a node into primary role and setting its disk to UpToDate with that */ drbd_send_uuids(mdev); - drbd_send_state(mdev); + drbd_send_current_state(mdev); } } @@ -3776,6 +3822,13 @@ static void drbd_disconnect(struct drbd_conf *mdev) if (mdev->state.conn == C_STANDALONE) return; + /* We are about to start the cleanup after connection loss. + * Make sure drbd_make_request knows about that. + * Usually we should be in some network failure state already, + * but just in case we are not, we fix it up here. + */ + drbd_force_state(mdev, NS(conn, C_NETWORK_FAILURE)); + /* asender does not clean up anything. it must not interfere, either */ drbd_thread_stop(&mdev->asender); drbd_free_sock(mdev); @@ -3803,8 +3856,6 @@ static void drbd_disconnect(struct drbd_conf *mdev) atomic_set(&mdev->rs_pending_cnt, 0); wake_up(&mdev->misc_wait); - del_timer(&mdev->request_timer); - /* make sure syncer is stopped and w_resume_next_sg queued */ del_timer_sync(&mdev->resync_timer); resync_timer_fn((unsigned long)mdev); @@ -4433,7 +4484,7 @@ static int got_BarrierAck(struct drbd_conf *mdev, struct p_header80 *h) if (mdev->state.conn == C_AHEAD && atomic_read(&mdev->ap_in_flight) == 0 && - !test_and_set_bit(AHEAD_TO_SYNC_SOURCE, &mdev->current_epoch->flags)) { + !test_and_set_bit(AHEAD_TO_SYNC_SOURCE, &mdev->flags)) { mdev->start_resync_timer.expires = jiffies + HZ; add_timer(&mdev->start_resync_timer); } diff --git a/drivers/block/drbd/drbd_req.c b/drivers/block/drbd/drbd_req.c index 4a0f314086e..9c5c84946b0 100644 --- a/drivers/block/drbd/drbd_req.c +++ b/drivers/block/drbd/drbd_req.c @@ -37,6 +37,7 @@ static void _drbd_start_io_acct(struct drbd_conf *mdev, struct drbd_request *req const int rw = bio_data_dir(bio); int cpu; cpu = part_stat_lock(); + part_round_stats(cpu, &mdev->vdisk->part0); part_stat_inc(cpu, &mdev->vdisk->part0, ios[rw]); part_stat_add(cpu, &mdev->vdisk->part0, sectors[rw], bio_sectors(bio)); part_inc_in_flight(&mdev->vdisk->part0, rw); @@ -214,8 +215,7 @@ void _req_may_be_done(struct drbd_request *req, struct bio_and_error *m) { const unsigned long s = req->rq_state; struct drbd_conf *mdev = req->mdev; - /* only WRITES may end up here without a master bio (on barrier ack) */ - int rw = req->master_bio ? bio_data_dir(req->master_bio) : WRITE; + int rw = req->rq_state & RQ_WRITE ? WRITE : READ; /* we must not complete the master bio, while it is * still being processed by _drbd_send_zc_bio (drbd_send_dblock) @@ -230,7 +230,7 @@ void _req_may_be_done(struct drbd_request *req, struct bio_and_error *m) return; if (s & RQ_NET_PENDING) return; - if (s & RQ_LOCAL_PENDING) + if (s & RQ_LOCAL_PENDING && !(s & RQ_LOCAL_ABORTED)) return; if (req->master_bio) { @@ -277,6 +277,9 @@ void _req_may_be_done(struct drbd_request *req, struct bio_and_error *m) req->master_bio = NULL; } + if (s & RQ_LOCAL_PENDING) + return; + if ((s & RQ_NET_MASK) == 0 || (s & RQ_NET_DONE)) { /* this is disconnected (local only) operation, * or protocol C P_WRITE_ACK, @@ -429,7 +432,7 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, break; case completed_ok: - if (bio_data_dir(req->master_bio) == WRITE) + if (req->rq_state & RQ_WRITE) mdev->writ_cnt += req->size>>9; else mdev->read_cnt += req->size>>9; @@ -438,7 +441,14 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, req->rq_state &= ~RQ_LOCAL_PENDING; _req_may_be_done_not_susp(req, m); - put_ldev(mdev); + break; + + case abort_disk_io: + req->rq_state |= RQ_LOCAL_ABORTED; + if (req->rq_state & RQ_WRITE) + _req_may_be_done_not_susp(req, m); + else + goto goto_queue_for_net_read; break; case write_completed_with_error: @@ -447,7 +457,6 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, __drbd_chk_io_error(mdev, false); _req_may_be_done_not_susp(req, m); - put_ldev(mdev); break; case read_ahead_completed_with_error: @@ -455,7 +464,6 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, req->rq_state |= RQ_LOCAL_COMPLETED; req->rq_state &= ~RQ_LOCAL_PENDING; _req_may_be_done_not_susp(req, m); - put_ldev(mdev); break; case read_completed_with_error: @@ -467,7 +475,8 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, D_ASSERT(!(req->rq_state & RQ_NET_MASK)); __drbd_chk_io_error(mdev, false); - put_ldev(mdev); + + goto_queue_for_net_read: /* no point in retrying if there is no good remote data, * or we have no connection. */ @@ -556,10 +565,8 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, drbd_queue_work(&mdev->data.work, &req->w); break; - case oos_handed_to_network: - /* actually the same */ + case read_retry_remote_canceled: case send_canceled: - /* treat it the same */ case send_failed: /* real cleanup will be done from tl_clear. just update flags * so it is no longer marked as on the worker queue */ @@ -589,17 +596,17 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, } req->rq_state &= ~RQ_NET_QUEUED; req->rq_state |= RQ_NET_SENT; - /* because _drbd_send_zc_bio could sleep, and may want to - * dereference the bio even after the "write_acked_by_peer" and - * "completed_ok" events came in, once we return from - * _drbd_send_zc_bio (drbd_send_dblock), we have to check - * whether it is done already, and end it. */ _req_may_be_done_not_susp(req, m); break; - case read_retry_remote_canceled: + case oos_handed_to_network: + /* Was not set PENDING, no longer QUEUED, so is now DONE + * as far as this connection is concerned. */ req->rq_state &= ~RQ_NET_QUEUED; - /* fall through, in case we raced with drbd_disconnect */ + req->rq_state |= RQ_NET_DONE; + _req_may_be_done_not_susp(req, m); + break; + case connection_lost_while_pending: /* transfer log cleanup after connection loss */ /* assert something? */ @@ -616,8 +623,6 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, _req_may_be_done(req, m); /* Allowed while state.susp */ break; - case write_acked_by_peer_and_sis: - req->rq_state |= RQ_NET_SIS; case conflict_discarded_by_peer: /* for discarded conflicting writes of multiple primaries, * there is no need to keep anything in the tl, potential @@ -628,18 +633,15 @@ int __req_mod(struct drbd_request *req, enum drbd_req_event what, (unsigned long long)req->sector, req->size); req->rq_state |= RQ_NET_DONE; /* fall through */ + case write_acked_by_peer_and_sis: case write_acked_by_peer: + if (what == write_acked_by_peer_and_sis) + req->rq_state |= RQ_NET_SIS; /* protocol C; successfully written on peer. - * Nothing to do here. + * Nothing more to do here. * We want to keep the tl in place for all protocols, to cater - * for volatile write-back caches on lower level devices. - * - * A barrier request is expected to have forced all prior - * requests onto stable storage, so completion of a barrier - * request could set NET_DONE right here, and not wait for the - * P_BARRIER_ACK, but that is an unnecessary optimization. */ + * for volatile write-back caches on lower level devices. */ - /* this makes it effectively the same as for: */ case recv_acked_by_peer: /* protocol B; pretends to be successfully written on peer. * see also notes above in handed_over_to_network about @@ -773,6 +775,7 @@ static int drbd_make_request_common(struct drbd_conf *mdev, struct bio *bio, uns int local, remote, send_oos = 0; int err = -EIO; int ret = 0; + union drbd_state s; /* allocate outside of all locks; */ req = drbd_req_new(mdev, bio); @@ -834,8 +837,9 @@ static int drbd_make_request_common(struct drbd_conf *mdev, struct bio *bio, uns drbd_al_begin_io(mdev, sector); } - remote = remote && drbd_should_do_remote(mdev->state); - send_oos = rw == WRITE && drbd_should_send_oos(mdev->state); + s = mdev->state; + remote = remote && drbd_should_do_remote(s); + send_oos = rw == WRITE && drbd_should_send_oos(s); D_ASSERT(!(remote && send_oos)); if (!(local || remote) && !is_susp(mdev->state)) { @@ -867,7 +871,7 @@ allocate_barrier: if (is_susp(mdev->state)) { /* If we got suspended, use the retry mechanism of - generic_make_request() to restart processing of this + drbd_make_request() to restart processing of this bio. In the next call to drbd_make_request we sleep in inc_ap_bio() */ ret = 1; @@ -1091,7 +1095,6 @@ void drbd_make_request(struct request_queue *q, struct bio *bio) */ D_ASSERT(bio->bi_size > 0); D_ASSERT((bio->bi_size & 0x1ff) == 0); - D_ASSERT(bio->bi_idx == 0); /* to make some things easier, force alignment of requests within the * granularity of our hash tables */ @@ -1099,8 +1102,9 @@ void drbd_make_request(struct request_queue *q, struct bio *bio) e_enr = (bio->bi_sector+(bio->bi_size>>9)-1) >> HT_SHIFT; if (likely(s_enr == e_enr)) { - inc_ap_bio(mdev, 1); - drbd_make_request_common(mdev, bio, start_time); + do { + inc_ap_bio(mdev, 1); + } while (drbd_make_request_common(mdev, bio, start_time)); return; } @@ -1196,36 +1200,66 @@ void request_timer_fn(unsigned long data) struct drbd_conf *mdev = (struct drbd_conf *) data; struct drbd_request *req; /* oldest request */ struct list_head *le; - unsigned long et = 0; /* effective timeout = ko_count * timeout */ + unsigned long ent = 0, dt = 0, et, nt; /* effective timeout = ko_count * timeout */ + unsigned long now; if (get_net_conf(mdev)) { - et = mdev->net_conf->timeout*HZ/10 * mdev->net_conf->ko_count; + if (mdev->state.conn >= C_WF_REPORT_PARAMS) + ent = mdev->net_conf->timeout*HZ/10 + * mdev->net_conf->ko_count; put_net_conf(mdev); } - if (!et || mdev->state.conn < C_WF_REPORT_PARAMS) + if (get_ldev(mdev)) { /* implicit state.disk >= D_INCONSISTENT */ + dt = mdev->ldev->dc.disk_timeout * HZ / 10; + put_ldev(mdev); + } + et = min_not_zero(dt, ent); + + if (!et) return; /* Recurring timer stopped */ + now = jiffies; + spin_lock_irq(&mdev->req_lock); le = &mdev->oldest_tle->requests; if (list_empty(le)) { spin_unlock_irq(&mdev->req_lock); - mod_timer(&mdev->request_timer, jiffies + et); + mod_timer(&mdev->request_timer, now + et); return; } le = le->prev; req = list_entry(le, struct drbd_request, tl_requests); - if (time_is_before_eq_jiffies(req->start_time + et)) { - if (req->rq_state & RQ_NET_PENDING) { - dev_warn(DEV, "Remote failed to finish a request within ko-count * timeout\n"); - _drbd_set_state(_NS(mdev, conn, C_TIMEOUT), CS_VERBOSE, NULL); - } else { - dev_warn(DEV, "Local backing block device frozen?\n"); - mod_timer(&mdev->request_timer, jiffies + et); - } - } else { - mod_timer(&mdev->request_timer, req->start_time + et); - } + /* The request is considered timed out, if + * - we have some effective timeout from the configuration, + * with above state restrictions applied, + * - the oldest request is waiting for a response from the network + * resp. the local disk, + * - the oldest request is in fact older than the effective timeout, + * - the connection was established (resp. disk was attached) + * for longer than the timeout already. + * Note that for 32bit jiffies and very stable connections/disks, + * we may have a wrap around, which is catched by + * !time_in_range(now, last_..._jif, last_..._jif + timeout). + * + * Side effect: once per 32bit wrap-around interval, which means every + * ~198 days with 250 HZ, we have a window where the timeout would need + * to expire twice (worst case) to become effective. Good enough. + */ + if (ent && req->rq_state & RQ_NET_PENDING && + time_after(now, req->start_time + ent) && + !time_in_range(now, mdev->last_reconnect_jif, mdev->last_reconnect_jif + ent)) { + dev_warn(DEV, "Remote failed to finish a request within ko-count * timeout\n"); + _drbd_set_state(_NS(mdev, conn, C_TIMEOUT), CS_VERBOSE | CS_HARD, NULL); + } + if (dt && req->rq_state & RQ_LOCAL_PENDING && + time_after(now, req->start_time + dt) && + !time_in_range(now, mdev->last_reattach_jif, mdev->last_reattach_jif + dt)) { + dev_warn(DEV, "Local backing device failed to meet the disk-timeout\n"); + __drbd_chk_io_error(mdev, 1); + } + nt = (time_after(now, req->start_time + et) ? now : req->start_time) + et; spin_unlock_irq(&mdev->req_lock); + mod_timer(&mdev->request_timer, nt); } diff --git a/drivers/block/drbd/drbd_req.h b/drivers/block/drbd/drbd_req.h index 68a234a5fdc..3d211191948 100644 --- a/drivers/block/drbd/drbd_req.h +++ b/drivers/block/drbd/drbd_req.h @@ -105,6 +105,7 @@ enum drbd_req_event { read_completed_with_error, read_ahead_completed_with_error, write_completed_with_error, + abort_disk_io, completed_ok, resend, fail_frozen_disk_io, @@ -118,18 +119,21 @@ enum drbd_req_event { * same time, so we should hold the request lock anyways. */ enum drbd_req_state_bits { - /* 210 - * 000: no local possible - * 001: to be submitted + /* 3210 + * 0000: no local possible + * 0001: to be submitted * UNUSED, we could map: 011: submitted, completion still pending - * 110: completed ok - * 010: completed with error + * 0110: completed ok + * 0010: completed with error + * 1001: Aborted (before completion) + * 1x10: Aborted and completed -> free */ __RQ_LOCAL_PENDING, __RQ_LOCAL_COMPLETED, __RQ_LOCAL_OK, + __RQ_LOCAL_ABORTED, - /* 76543 + /* 87654 * 00000: no network possible * 00001: to be send * 00011: to be send, on worker queue @@ -199,8 +203,9 @@ enum drbd_req_state_bits { #define RQ_LOCAL_PENDING (1UL << __RQ_LOCAL_PENDING) #define RQ_LOCAL_COMPLETED (1UL << __RQ_LOCAL_COMPLETED) #define RQ_LOCAL_OK (1UL << __RQ_LOCAL_OK) +#define RQ_LOCAL_ABORTED (1UL << __RQ_LOCAL_ABORTED) -#define RQ_LOCAL_MASK ((RQ_LOCAL_OK << 1)-1) /* 0x07 */ +#define RQ_LOCAL_MASK ((RQ_LOCAL_ABORTED << 1)-1) #define RQ_NET_PENDING (1UL << __RQ_NET_PENDING) #define RQ_NET_QUEUED (1UL << __RQ_NET_QUEUED) diff --git a/drivers/block/drbd/drbd_worker.c b/drivers/block/drbd/drbd_worker.c index 4d3e6f6213b..620c70ff223 100644 --- a/drivers/block/drbd/drbd_worker.c +++ b/drivers/block/drbd/drbd_worker.c @@ -70,11 +70,29 @@ rwlock_t global_state_lock; void drbd_md_io_complete(struct bio *bio, int error) { struct drbd_md_io *md_io; + struct drbd_conf *mdev; md_io = (struct drbd_md_io *)bio->bi_private; + mdev = container_of(md_io, struct drbd_conf, md_io); + md_io->error = error; - complete(&md_io->event); + /* We grabbed an extra reference in _drbd_md_sync_page_io() to be able + * to timeout on the lower level device, and eventually detach from it. + * If this io completion runs after that timeout expired, this + * drbd_md_put_buffer() may allow us to finally try and re-attach. + * During normal operation, this only puts that extra reference + * down to 1 again. + * Make sure we first drop the reference, and only then signal + * completion, or we may (in drbd_al_read_log()) cycle so fast into the + * next drbd_md_sync_page_io(), that we trigger the + * ASSERT(atomic_read(&mdev->md_io_in_use) == 1) there. + */ + drbd_md_put_buffer(mdev); + md_io->done = 1; + wake_up(&mdev->misc_wait); + bio_put(bio); + put_ldev(mdev); } /* reads on behalf of the partner, @@ -226,6 +244,7 @@ void drbd_endio_pri(struct bio *bio, int error) spin_lock_irqsave(&mdev->req_lock, flags); __req_mod(req, what, &m); spin_unlock_irqrestore(&mdev->req_lock, flags); + put_ldev(mdev); if (m.bio) complete_master_bio(mdev, &m); @@ -290,7 +309,7 @@ void drbd_csum_bio(struct drbd_conf *mdev, struct crypto_hash *tfm, struct bio * sg_init_table(&sg, 1); crypto_hash_init(&desc); - __bio_for_each_segment(bvec, bio, i, 0) { + bio_for_each_segment(bvec, bio, i) { sg_set_page(&sg, bvec->bv_page, bvec->bv_len, bvec->bv_offset); crypto_hash_update(&desc, &sg, sg.length); } @@ -728,7 +747,7 @@ int w_start_resync(struct drbd_conf *mdev, struct drbd_work *w, int cancel) } drbd_start_resync(mdev, C_SYNC_SOURCE); - clear_bit(AHEAD_TO_SYNC_SOURCE, &mdev->current_epoch->flags); + clear_bit(AHEAD_TO_SYNC_SOURCE, &mdev->flags); return 1; } @@ -1519,14 +1538,14 @@ void drbd_start_resync(struct drbd_conf *mdev, enum drbd_conns side) } drbd_state_lock(mdev); - + write_lock_irq(&global_state_lock); if (!get_ldev_if_state(mdev, D_NEGOTIATING)) { + write_unlock_irq(&global_state_lock); drbd_state_unlock(mdev); return; } - write_lock_irq(&global_state_lock); - ns = mdev->state; + ns.i = mdev->state.i; ns.aftr_isp = !_drbd_may_sync_now(mdev); diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index b0b00d70c16..cce7df367b7 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -551,7 +551,7 @@ static void floppy_ready(void); static void floppy_start(void); static void process_fd_request(void); static void recalibrate_floppy(void); -static void floppy_shutdown(unsigned long); +static void floppy_shutdown(struct work_struct *); static int floppy_request_regions(int); static void floppy_release_regions(int); @@ -588,6 +588,8 @@ static int buffer_max = -1; static struct floppy_fdc_state fdc_state[N_FDC]; static int fdc; /* current fdc */ +static struct workqueue_struct *floppy_wq; + static struct floppy_struct *_floppy = floppy_type; static unsigned char current_drive; static long current_count_sectors; @@ -629,16 +631,15 @@ static inline void set_debugt(void) { } static inline void debugt(const char *func, const char *msg) { } #endif /* DEBUGT */ -typedef void (*timeout_fn)(unsigned long); -static DEFINE_TIMER(fd_timeout, floppy_shutdown, 0, 0); +static DECLARE_DELAYED_WORK(fd_timeout, floppy_shutdown); static const char *timeout_message; static void is_alive(const char *func, const char *message) { /* this routine checks whether the floppy driver is "alive" */ if (test_bit(0, &fdc_busy) && command_status < 2 && - !timer_pending(&fd_timeout)) { + !delayed_work_pending(&fd_timeout)) { DPRINT("%s: timeout handler died. %s\n", func, message); } } @@ -666,15 +667,18 @@ static int output_log_pos; static void __reschedule_timeout(int drive, const char *message) { + unsigned long delay; + if (drive == current_reqD) drive = current_drive; - del_timer(&fd_timeout); + if (drive < 0 || drive >= N_DRIVE) { - fd_timeout.expires = jiffies + 20UL * HZ; + delay = 20UL * HZ; drive = 0; } else - fd_timeout.expires = jiffies + UDP->timeout; - add_timer(&fd_timeout); + delay = UDP->timeout; + + queue_delayed_work(floppy_wq, &fd_timeout, delay); if (UDP->flags & FD_DEBUG) DPRINT("reschedule timeout %s\n", message); timeout_message = message; @@ -872,7 +876,7 @@ static int lock_fdc(int drive, bool interruptible) command_status = FD_COMMAND_NONE; - __reschedule_timeout(drive, "lock fdc"); + reschedule_timeout(drive, "lock fdc"); set_fdc(drive); return 0; } @@ -880,23 +884,15 @@ static int lock_fdc(int drive, bool interruptible) /* unlocks the driver */ static void unlock_fdc(void) { - unsigned long flags; - - raw_cmd = NULL; if (!test_bit(0, &fdc_busy)) DPRINT("FDC access conflict!\n"); - if (do_floppy) - DPRINT("device interrupt still active at FDC release: %pf!\n", - do_floppy); + raw_cmd = NULL; command_status = FD_COMMAND_NONE; - spin_lock_irqsave(&floppy_lock, flags); - del_timer(&fd_timeout); + __cancel_delayed_work(&fd_timeout); + do_floppy = NULL; cont = NULL; clear_bit(0, &fdc_busy); - if (current_req || set_next_request()) - do_fd_request(current_req->q); - spin_unlock_irqrestore(&floppy_lock, flags); wake_up(&fdc_wait); } @@ -968,26 +964,24 @@ static DECLARE_WORK(floppy_work, NULL); static void schedule_bh(void (*handler)(void)) { + WARN_ON(work_pending(&floppy_work)); + PREPARE_WORK(&floppy_work, (work_func_t)handler); - schedule_work(&floppy_work); + queue_work(floppy_wq, &floppy_work); } -static DEFINE_TIMER(fd_timer, NULL, 0, 0); +static DECLARE_DELAYED_WORK(fd_timer, NULL); static void cancel_activity(void) { - unsigned long flags; - - spin_lock_irqsave(&floppy_lock, flags); do_floppy = NULL; - PREPARE_WORK(&floppy_work, (work_func_t)empty); - del_timer(&fd_timer); - spin_unlock_irqrestore(&floppy_lock, flags); + cancel_delayed_work_sync(&fd_timer); + cancel_work_sync(&floppy_work); } /* this function makes sure that the disk stays in the drive during the * transfer */ -static void fd_watchdog(void) +static void fd_watchdog(struct work_struct *arg) { debug_dcl(DP->flags, "calling disk change from watchdog\n"); @@ -997,21 +991,20 @@ static void fd_watchdog(void) cont->done(0); reset_fdc(); } else { - del_timer(&fd_timer); - fd_timer.function = (timeout_fn)fd_watchdog; - fd_timer.expires = jiffies + HZ / 10; - add_timer(&fd_timer); + cancel_delayed_work(&fd_timer); + PREPARE_DELAYED_WORK(&fd_timer, fd_watchdog); + queue_delayed_work(floppy_wq, &fd_timer, HZ / 10); } } static void main_command_interrupt(void) { - del_timer(&fd_timer); + cancel_delayed_work(&fd_timer); cont->interrupt(); } /* waits for a delay (spinup or select) to pass */ -static int fd_wait_for_completion(unsigned long delay, timeout_fn function) +static int fd_wait_for_completion(unsigned long expires, work_func_t function) { if (FDCS->reset) { reset_fdc(); /* do the reset during sleep to win time @@ -1020,11 +1013,10 @@ static int fd_wait_for_completion(unsigned long delay, timeout_fn function) return 1; } - if (time_before(jiffies, delay)) { - del_timer(&fd_timer); - fd_timer.function = function; - fd_timer.expires = delay; - add_timer(&fd_timer); + if (time_before(jiffies, expires)) { + cancel_delayed_work(&fd_timer); + PREPARE_DELAYED_WORK(&fd_timer, function); + queue_delayed_work(floppy_wq, &fd_timer, expires - jiffies); return 1; } return 0; @@ -1342,7 +1334,7 @@ static int fdc_dtr(void) */ FDCS->dtr = raw_cmd->rate & 3; return fd_wait_for_completion(jiffies + 2UL * HZ / 100, - (timeout_fn)floppy_ready); + (work_func_t)floppy_ready); } /* fdc_dtr */ static void tell_sector(void) @@ -1447,7 +1439,7 @@ static void setup_rw_floppy(void) int flags; int dflags; unsigned long ready_date; - timeout_fn function; + work_func_t function; flags = raw_cmd->flags; if (flags & (FD_RAW_READ | FD_RAW_WRITE)) @@ -1461,9 +1453,9 @@ static void setup_rw_floppy(void) */ if (time_after(ready_date, jiffies + DP->select_delay)) { ready_date -= DP->select_delay; - function = (timeout_fn)floppy_start; + function = (work_func_t)floppy_start; } else - function = (timeout_fn)setup_rw_floppy; + function = (work_func_t)setup_rw_floppy; /* wait until the floppy is spinning fast enough */ if (fd_wait_for_completion(ready_date, function)) @@ -1493,7 +1485,7 @@ static void setup_rw_floppy(void) inr = result(); cont->interrupt(); } else if (flags & FD_RAW_NEED_DISK) - fd_watchdog(); + fd_watchdog(NULL); } static int blind_seek; @@ -1802,20 +1794,22 @@ static void show_floppy(void) pr_info("do_floppy=%pf\n", do_floppy); if (work_pending(&floppy_work)) pr_info("floppy_work.func=%pf\n", floppy_work.func); - if (timer_pending(&fd_timer)) - pr_info("fd_timer.function=%pf\n", fd_timer.function); - if (timer_pending(&fd_timeout)) { - pr_info("timer_function=%pf\n", fd_timeout.function); - pr_info("expires=%lu\n", fd_timeout.expires - jiffies); - pr_info("now=%lu\n", jiffies); - } + if (delayed_work_pending(&fd_timer)) + pr_info("delayed work.function=%p expires=%ld\n", + fd_timer.work.func, + fd_timer.timer.expires - jiffies); + if (delayed_work_pending(&fd_timeout)) + pr_info("timer_function=%p expires=%ld\n", + fd_timeout.work.func, + fd_timeout.timer.expires - jiffies); + pr_info("cont=%p\n", cont); pr_info("current_req=%p\n", current_req); pr_info("command_status=%d\n", command_status); pr_info("\n"); } -static void floppy_shutdown(unsigned long data) +static void floppy_shutdown(struct work_struct *arg) { unsigned long flags; @@ -1868,7 +1862,7 @@ static int start_motor(void (*function)(void)) /* wait_for_completion also schedules reset if needed. */ return fd_wait_for_completion(DRS->select_date + DP->select_delay, - (timeout_fn)function); + (work_func_t)function); } static void floppy_ready(void) @@ -2821,7 +2815,6 @@ do_request: spin_lock_irq(&floppy_lock); pending = set_next_request(); spin_unlock_irq(&floppy_lock); - if (!pending) { do_floppy = NULL; unlock_fdc(); @@ -2898,13 +2891,15 @@ static void do_fd_request(struct request_queue *q) current_req->cmd_flags)) return; - if (test_bit(0, &fdc_busy)) { + if (test_and_set_bit(0, &fdc_busy)) { /* fdc busy, this new request will be treated when the current one is done */ is_alive(__func__, "old request running"); return; } - lock_fdc(MAXTIMEOUT, false); + command_status = FD_COMMAND_NONE; + __reschedule_timeout(MAXTIMEOUT, "fd_request"); + set_fdc(0); process_fd_request(); is_alive(__func__, ""); } @@ -3612,9 +3607,7 @@ static int floppy_release(struct gendisk *disk, fmode_t mode) mutex_lock(&floppy_mutex); mutex_lock(&open_lock); - if (UDRS->fd_ref < 0) - UDRS->fd_ref = 0; - else if (!UDRS->fd_ref--) { + if (!UDRS->fd_ref--) { DPRINT("floppy_release with fd_ref == 0"); UDRS->fd_ref = 0; } @@ -3650,13 +3643,7 @@ static int floppy_open(struct block_device *bdev, fmode_t mode) set_bit(FD_VERIFY_BIT, &UDRS->flags); } - if (UDRS->fd_ref == -1 || (UDRS->fd_ref && (mode & FMODE_EXCL))) - goto out2; - - if (mode & FMODE_EXCL) - UDRS->fd_ref = -1; - else - UDRS->fd_ref++; + UDRS->fd_ref++; opened_bdev[drive] = bdev; @@ -3719,10 +3706,8 @@ static int floppy_open(struct block_device *bdev, fmode_t mode) mutex_unlock(&floppy_mutex); return 0; out: - if (UDRS->fd_ref < 0) - UDRS->fd_ref = 0; - else - UDRS->fd_ref--; + UDRS->fd_ref--; + if (!UDRS->fd_ref) opened_bdev[drive] = NULL; out2: @@ -4159,10 +4144,16 @@ static int __init floppy_init(void) goto out_put_disk; } + floppy_wq = alloc_ordered_workqueue("floppy", 0); + if (!floppy_wq) { + err = -ENOMEM; + goto out_put_disk; + } + disks[dr]->queue = blk_init_queue(do_fd_request, &floppy_lock); if (!disks[dr]->queue) { err = -ENOMEM; - goto out_put_disk; + goto out_destroy_workq; } blk_queue_max_hw_sectors(disks[dr]->queue, 64); @@ -4213,7 +4204,7 @@ static int __init floppy_init(void) use_virtual_dma = can_use_virtual_dma & 1; fdc_state[0].address = FDC1; if (fdc_state[0].address == -1) { - del_timer_sync(&fd_timeout); + cancel_delayed_work(&fd_timeout); err = -ENODEV; goto out_unreg_region; } @@ -4224,7 +4215,7 @@ static int __init floppy_init(void) fdc = 0; /* reset fdc in case of unexpected interrupt */ err = floppy_grab_irq_and_dma(); if (err) { - del_timer_sync(&fd_timeout); + cancel_delayed_work(&fd_timeout); err = -EBUSY; goto out_unreg_region; } @@ -4281,13 +4272,13 @@ static int __init floppy_init(void) user_reset_fdc(-1, FD_RESET_ALWAYS, false); } fdc = 0; - del_timer_sync(&fd_timeout); + cancel_delayed_work(&fd_timeout); current_drive = 0; initialized = true; if (have_no_fdc) { DPRINT("no floppy controllers found\n"); err = have_no_fdc; - goto out_flush_work; + goto out_release_dma; } for (drive = 0; drive < N_DRIVE; drive++) { @@ -4302,7 +4293,7 @@ static int __init floppy_init(void) err = platform_device_register(&floppy_device[drive]); if (err) - goto out_flush_work; + goto out_release_dma; err = device_create_file(&floppy_device[drive].dev, &dev_attr_cmos); @@ -4320,13 +4311,14 @@ static int __init floppy_init(void) out_unreg_platform_dev: platform_device_unregister(&floppy_device[drive]); -out_flush_work: - flush_work_sync(&floppy_work); +out_release_dma: if (atomic_read(&usage_count)) floppy_release_irq_and_dma(); out_unreg_region: blk_unregister_region(MKDEV(FLOPPY_MAJOR, 0), 256); platform_driver_unregister(&floppy_driver); +out_destroy_workq: + destroy_workqueue(floppy_wq); out_unreg_blkdev: unregister_blkdev(FLOPPY_MAJOR, "fd"); out_put_disk: @@ -4397,7 +4389,7 @@ static int floppy_grab_irq_and_dma(void) * We might have scheduled a free_irq(), wait it to * drain first: */ - flush_work_sync(&floppy_work); + flush_workqueue(floppy_wq); if (fd_request_irq()) { DPRINT("Unable to grab IRQ%d for the floppy driver\n", @@ -4488,9 +4480,9 @@ static void floppy_release_irq_and_dma(void) pr_info("motor off timer %d still active\n", drive); #endif - if (timer_pending(&fd_timeout)) + if (delayed_work_pending(&fd_timeout)) pr_info("floppy timer still active:%s\n", timeout_message); - if (timer_pending(&fd_timer)) + if (delayed_work_pending(&fd_timer)) pr_info("auxiliary floppy timer still active\n"); if (work_pending(&floppy_work)) pr_info("work still pending\n"); @@ -4560,8 +4552,9 @@ static void __exit floppy_module_exit(void) put_disk(disks[drive]); } - del_timer_sync(&fd_timeout); - del_timer_sync(&fd_timer); + cancel_delayed_work_sync(&fd_timeout); + cancel_delayed_work_sync(&fd_timer); + destroy_workqueue(floppy_wq); if (atomic_read(&usage_count)) floppy_release_irq_and_dma(); diff --git a/drivers/block/mtip32xx/mtip32xx.c b/drivers/block/mtip32xx/mtip32xx.c index 304000c3d43..264bc77dcb9 100644 --- a/drivers/block/mtip32xx/mtip32xx.c +++ b/drivers/block/mtip32xx/mtip32xx.c @@ -294,18 +294,16 @@ static int hba_reset_nosleep(struct driver_data *dd) */ static inline void mtip_issue_ncq_command(struct mtip_port *port, int tag) { - unsigned long flags = 0; - atomic_set(&port->commands[tag].active, 1); - spin_lock_irqsave(&port->cmd_issue_lock, flags); + spin_lock(&port->cmd_issue_lock); writel((1 << MTIP_TAG_BIT(tag)), port->s_active[MTIP_TAG_INDEX(tag)]); writel((1 << MTIP_TAG_BIT(tag)), port->cmd_issue[MTIP_TAG_INDEX(tag)]); - spin_unlock_irqrestore(&port->cmd_issue_lock, flags); + spin_unlock(&port->cmd_issue_lock); /* Set the command's timeout value.*/ port->commands[tag].comp_time = jiffies + msecs_to_jiffies( @@ -436,8 +434,7 @@ static void mtip_init_port(struct mtip_port *port) writel(0xFFFFFFFF, port->completed[i]); /* Clear any pending interrupts for this port */ - writel(readl(port->dd->mmio + PORT_IRQ_STAT), - port->dd->mmio + PORT_IRQ_STAT); + writel(readl(port->mmio + PORT_IRQ_STAT), port->mmio + PORT_IRQ_STAT); /* Clear any pending interrupts on the HBA. */ writel(readl(port->dd->mmio + HOST_IRQ_STAT), @@ -782,13 +779,24 @@ static void mtip_handle_tfe(struct driver_data *dd) /* Stop the timer to prevent command timeouts. */ del_timer(&port->cmd_timer); + set_bit(MTIP_PF_EH_ACTIVE_BIT, &port->flags); + + if (test_bit(MTIP_PF_IC_ACTIVE_BIT, &port->flags) && + test_bit(MTIP_TAG_INTERNAL, port->allocated)) { + cmd = &port->commands[MTIP_TAG_INTERNAL]; + dbg_printk(MTIP_DRV_NAME " TFE for the internal command\n"); + + atomic_inc(&cmd->active); /* active > 1 indicates error */ + if (cmd->comp_data && cmd->comp_func) { + cmd->comp_func(port, MTIP_TAG_INTERNAL, + cmd->comp_data, PORT_IRQ_TF_ERR); + } + goto handle_tfe_exit; + } /* clear the tag accumulator */ memset(tagaccum, 0, SLOTBITS_IN_LONGS * sizeof(long)); - /* Set eh_active */ - set_bit(MTIP_PF_EH_ACTIVE_BIT, &port->flags); - /* Loop through all the groups */ for (group = 0; group < dd->slot_groups; group++) { completed = readl(port->completed[group]); @@ -940,6 +948,7 @@ static void mtip_handle_tfe(struct driver_data *dd) } print_tags(dd, "reissued (TFE)", tagaccum, cmd_cnt); +handle_tfe_exit: /* clear eh_active */ clear_bit(MTIP_PF_EH_ACTIVE_BIT, &port->flags); wake_up_interruptible(&port->svc_wait); @@ -961,6 +970,8 @@ static inline void mtip_process_sdbf(struct driver_data *dd) /* walk all bits in all slot groups */ for (group = 0; group < dd->slot_groups; group++) { completed = readl(port->completed[group]); + if (!completed) + continue; /* clear completed status register in the hardware.*/ writel(completed, port->completed[group]); @@ -1329,22 +1340,6 @@ static int mtip_exec_internal_command(struct mtip_port *port, } rv = -EAGAIN; } - - if (readl(port->cmd_issue[MTIP_TAG_INTERNAL]) - & (1 << MTIP_TAG_INTERNAL)) { - dev_warn(&port->dd->pdev->dev, - "Retiring internal command but CI is 1.\n"); - if (test_bit(MTIP_DDF_REMOVE_PENDING_BIT, - &port->dd->dd_flag)) { - hba_reset_nosleep(port->dd); - rv = -ENXIO; - } else { - mtip_restart_port(port); - rv = -EAGAIN; - } - goto exec_ic_exit; - } - } else { /* Spin for <timeout> checking if command still outstanding */ timeout = jiffies + msecs_to_jiffies(timeout); @@ -1361,21 +1356,25 @@ static int mtip_exec_internal_command(struct mtip_port *port, rv = -ENXIO; goto exec_ic_exit; } + if (readl(port->mmio + PORT_IRQ_STAT) & PORT_IRQ_ERR) { + atomic_inc(&int_cmd->active); /* error */ + break; + } } + } - if (readl(port->cmd_issue[MTIP_TAG_INTERNAL]) + if (atomic_read(&int_cmd->active) > 1) { + dev_err(&port->dd->pdev->dev, + "Internal command [%02X] failed\n", fis->command); + rv = -EIO; + } + if (readl(port->cmd_issue[MTIP_TAG_INTERNAL]) & (1 << MTIP_TAG_INTERNAL)) { - dev_err(&port->dd->pdev->dev, - "Internal command did not complete [atomic]\n"); + rv = -ENXIO; + if (!test_bit(MTIP_DDF_REMOVE_PENDING_BIT, + &port->dd->dd_flag)) { + mtip_restart_port(port); rv = -EAGAIN; - if (test_bit(MTIP_DDF_REMOVE_PENDING_BIT, - &port->dd->dd_flag)) { - hba_reset_nosleep(port->dd); - rv = -ENXIO; - } else { - mtip_restart_port(port); - rv = -EAGAIN; - } } } exec_ic_exit: @@ -1893,13 +1892,33 @@ static int exec_drive_command(struct mtip_port *port, u8 *command, void __user *user_buffer) { struct host_to_dev_fis fis; - struct host_to_dev_fis *reply = (port->rxfis + RX_FIS_D2H_REG); + struct host_to_dev_fis *reply; + u8 *buf = NULL; + dma_addr_t dma_addr = 0; + int rv = 0, xfer_sz = command[3]; + + if (xfer_sz) { + if (user_buffer) + return -EFAULT; + + buf = dmam_alloc_coherent(&port->dd->pdev->dev, + ATA_SECT_SIZE * xfer_sz, + &dma_addr, + GFP_KERNEL); + if (!buf) { + dev_err(&port->dd->pdev->dev, + "Memory allocation failed (%d bytes)\n", + ATA_SECT_SIZE * xfer_sz); + return -ENOMEM; + } + memset(buf, 0, ATA_SECT_SIZE * xfer_sz); + } /* Build the FIS. */ memset(&fis, 0, sizeof(struct host_to_dev_fis)); - fis.type = 0x27; - fis.opts = 1 << 7; - fis.command = command[0]; + fis.type = 0x27; + fis.opts = 1 << 7; + fis.command = command[0]; fis.features = command[2]; fis.sect_count = command[3]; if (fis.command == ATA_CMD_SMART) { @@ -1908,6 +1927,11 @@ static int exec_drive_command(struct mtip_port *port, u8 *command, fis.cyl_hi = 0xC2; } + if (xfer_sz) + reply = (port->rxfis + RX_FIS_PIO_SETUP); + else + reply = (port->rxfis + RX_FIS_D2H_REG); + dbg_printk(MTIP_DRV_NAME " %s: User Command: cmd %x, sect %x, " "feat %x, sectcnt %x\n", @@ -1917,43 +1941,46 @@ static int exec_drive_command(struct mtip_port *port, u8 *command, command[2], command[3]); - memset(port->sector_buffer, 0x00, ATA_SECT_SIZE); - /* Execute the command. */ if (mtip_exec_internal_command(port, &fis, 5, - port->sector_buffer_dma, - (command[3] != 0) ? ATA_SECT_SIZE : 0, + (xfer_sz ? dma_addr : 0), + (xfer_sz ? ATA_SECT_SIZE * xfer_sz : 0), 0, GFP_KERNEL, MTIP_IOCTL_COMMAND_TIMEOUT_MS) < 0) { - return -1; + rv = -EFAULT; + goto exit_drive_command; } /* Collect the completion status. */ command[0] = reply->command; /* Status*/ command[1] = reply->features; /* Error*/ - command[2] = command[3]; + command[2] = reply->sect_count; dbg_printk(MTIP_DRV_NAME " %s: Completion Status: stat %x, " - "err %x, cmd %x\n", + "err %x, nsect %x\n", __func__, command[0], command[1], command[2]); - if (user_buffer && command[3]) { + if (xfer_sz) { if (copy_to_user(user_buffer, - port->sector_buffer, + buf, ATA_SECT_SIZE * command[3])) { - return -EFAULT; + rv = -EFAULT; + goto exit_drive_command; } } - - return 0; +exit_drive_command: + if (buf) + dmam_free_coherent(&port->dd->pdev->dev, + ATA_SECT_SIZE * xfer_sz, buf, dma_addr); + return rv; } /* @@ -2003,6 +2030,32 @@ static unsigned int implicit_sector(unsigned char command, return rv; } +static void mtip_set_timeout(struct host_to_dev_fis *fis, unsigned int *timeout) +{ + switch (fis->command) { + case ATA_CMD_DOWNLOAD_MICRO: + *timeout = 120000; /* 2 minutes */ + break; + case ATA_CMD_SEC_ERASE_UNIT: + case 0xFC: + *timeout = 240000; /* 4 minutes */ + break; + case ATA_CMD_STANDBYNOW1: + *timeout = 10000; /* 10 seconds */ + break; + case 0xF7: + case 0xFA: + *timeout = 60000; /* 60 seconds */ + break; + case ATA_CMD_SMART: + *timeout = 15000; /* 15 seconds */ + break; + default: + *timeout = MTIP_IOCTL_COMMAND_TIMEOUT_MS; + break; + } +} + /* * Executes a taskfile * See ide_taskfile_ioctl() for derivation @@ -2023,7 +2076,7 @@ static int exec_drive_taskfile(struct driver_data *dd, unsigned int taskin = 0; unsigned int taskout = 0; u8 nsect = 0; - unsigned int timeout = MTIP_IOCTL_COMMAND_TIMEOUT_MS; + unsigned int timeout; unsigned int force_single_sector; unsigned int transfer_size; unsigned long task_file_data; @@ -2153,32 +2206,7 @@ static int exec_drive_taskfile(struct driver_data *dd, fis.lba_hi, fis.device); - switch (fis.command) { - case ATA_CMD_DOWNLOAD_MICRO: - /* Change timeout for Download Microcode to 2 minutes */ - timeout = 120000; - break; - case ATA_CMD_SEC_ERASE_UNIT: - /* Change timeout for Security Erase Unit to 4 minutes.*/ - timeout = 240000; - break; - case ATA_CMD_STANDBYNOW1: - /* Change timeout for standby immediate to 10 seconds.*/ - timeout = 10000; - break; - case 0xF7: - case 0xFA: - /* Change timeout for vendor unique command to 10 secs */ - timeout = 10000; - break; - case ATA_CMD_SMART: - /* Change timeout for vendor unique command to 15 secs */ - timeout = 15000; - break; - default: - timeout = MTIP_IOCTL_COMMAND_TIMEOUT_MS; - break; - } + mtip_set_timeout(&fis, &timeout); /* Determine the correct transfer size.*/ if (force_single_sector) @@ -2295,13 +2323,12 @@ static int mtip_hw_ioctl(struct driver_data *dd, unsigned int cmd, { switch (cmd) { case HDIO_GET_IDENTITY: - if (mtip_get_identify(dd->port, (void __user *) arg) < 0) { - dev_warn(&dd->pdev->dev, - "Unable to read identity\n"); - return -EIO; - } - + { + if (copy_to_user((void __user *)arg, dd->port->identify, + sizeof(u16) * ATA_ID_WORDS)) + return -EFAULT; break; + } case HDIO_DRIVE_CMD: { u8 drive_command[4]; @@ -2537,40 +2564,58 @@ static ssize_t mtip_hw_show_registers(struct device *dev, int size = 0; int n; - size += sprintf(&buf[size], "S ACTive:\n"); + size += sprintf(&buf[size], "Hardware\n--------\n"); + size += sprintf(&buf[size], "S ACTive : [ 0x"); - for (n = 0; n < dd->slot_groups; n++) - size += sprintf(&buf[size], "0x%08x\n", + for (n = dd->slot_groups-1; n >= 0; n--) + size += sprintf(&buf[size], "%08X ", readl(dd->port->s_active[n])); - size += sprintf(&buf[size], "Command Issue:\n"); + size += sprintf(&buf[size], "]\n"); + size += sprintf(&buf[size], "Command Issue : [ 0x"); - for (n = 0; n < dd->slot_groups; n++) - size += sprintf(&buf[size], "0x%08x\n", + for (n = dd->slot_groups-1; n >= 0; n--) + size += sprintf(&buf[size], "%08X ", readl(dd->port->cmd_issue[n])); - size += sprintf(&buf[size], "Allocated:\n"); + size += sprintf(&buf[size], "]\n"); + size += sprintf(&buf[size], "Completed : [ 0x"); + + for (n = dd->slot_groups-1; n >= 0; n--) + size += sprintf(&buf[size], "%08X ", + readl(dd->port->completed[n])); + + size += sprintf(&buf[size], "]\n"); + size += sprintf(&buf[size], "PORT IRQ STAT : [ 0x%08X ]\n", + readl(dd->port->mmio + PORT_IRQ_STAT)); + size += sprintf(&buf[size], "HOST IRQ STAT : [ 0x%08X ]\n", + readl(dd->mmio + HOST_IRQ_STAT)); + size += sprintf(&buf[size], "\n"); - for (n = 0; n < dd->slot_groups; n++) { + size += sprintf(&buf[size], "Local\n-----\n"); + size += sprintf(&buf[size], "Allocated : [ 0x"); + + for (n = dd->slot_groups-1; n >= 0; n--) { if (sizeof(long) > sizeof(u32)) group_allocated = dd->port->allocated[n/2] >> (32*(n&1)); else group_allocated = dd->port->allocated[n]; - size += sprintf(&buf[size], "0x%08x\n", - group_allocated); + size += sprintf(&buf[size], "%08X ", group_allocated); } + size += sprintf(&buf[size], "]\n"); - size += sprintf(&buf[size], "Completed:\n"); - - for (n = 0; n < dd->slot_groups; n++) - size += sprintf(&buf[size], "0x%08x\n", - readl(dd->port->completed[n])); + size += sprintf(&buf[size], "Commands in Q: [ 0x"); - size += sprintf(&buf[size], "PORT IRQ STAT : 0x%08x\n", - readl(dd->port->mmio + PORT_IRQ_STAT)); - size += sprintf(&buf[size], "HOST IRQ STAT : 0x%08x\n", - readl(dd->mmio + HOST_IRQ_STAT)); + for (n = dd->slot_groups-1; n >= 0; n--) { + if (sizeof(long) > sizeof(u32)) + group_allocated = + dd->port->cmds_to_issue[n/2] >> (32*(n&1)); + else + group_allocated = dd->port->cmds_to_issue[n]; + size += sprintf(&buf[size], "%08X ", group_allocated); + } + size += sprintf(&buf[size], "]\n"); return size; } @@ -2592,8 +2637,24 @@ static ssize_t mtip_hw_show_status(struct device *dev, return size; } +static ssize_t mtip_hw_show_flags(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct driver_data *dd = dev_to_disk(dev)->private_data; + int size = 0; + + size += sprintf(&buf[size], "Flag in port struct : [ %08lX ]\n", + dd->port->flags); + size += sprintf(&buf[size], "Flag in dd struct : [ %08lX ]\n", + dd->dd_flag); + + return size; +} + static DEVICE_ATTR(registers, S_IRUGO, mtip_hw_show_registers, NULL); static DEVICE_ATTR(status, S_IRUGO, mtip_hw_show_status, NULL); +static DEVICE_ATTR(flags, S_IRUGO, mtip_hw_show_flags, NULL); /* * Create the sysfs related attributes. @@ -2616,6 +2677,9 @@ static int mtip_hw_sysfs_init(struct driver_data *dd, struct kobject *kobj) if (sysfs_create_file(kobj, &dev_attr_status.attr)) dev_warn(&dd->pdev->dev, "Error creating 'status' sysfs entry\n"); + if (sysfs_create_file(kobj, &dev_attr_flags.attr)) + dev_warn(&dd->pdev->dev, + "Error creating 'flags' sysfs entry\n"); return 0; } @@ -2636,6 +2700,7 @@ static int mtip_hw_sysfs_exit(struct driver_data *dd, struct kobject *kobj) sysfs_remove_file(kobj, &dev_attr_registers.attr); sysfs_remove_file(kobj, &dev_attr_status.attr); + sysfs_remove_file(kobj, &dev_attr_flags.attr); return 0; } @@ -3634,7 +3699,10 @@ skip_create_disk: set_bit(QUEUE_FLAG_NONROT, &dd->queue->queue_flags); blk_queue_max_segments(dd->queue, MTIP_MAX_SG); blk_queue_physical_block_size(dd->queue, 4096); + blk_queue_max_hw_sectors(dd->queue, 0xffff); + blk_queue_max_segment_size(dd->queue, 0x400000); blk_queue_io_min(dd->queue, 4096); + /* * write back cache is not supported in the device. FUA depends on * write back cache support, hence setting flush support to zero. diff --git a/drivers/block/mtip32xx/mtip32xx.h b/drivers/block/mtip32xx/mtip32xx.h index 4ef58336310..b2c88da26b2 100644 --- a/drivers/block/mtip32xx/mtip32xx.h +++ b/drivers/block/mtip32xx/mtip32xx.h @@ -113,33 +113,35 @@ #define __force_bit2int (unsigned int __force) -/* below are bit numbers in 'flags' defined in mtip_port */ -#define MTIP_PF_IC_ACTIVE_BIT 0 /* pio/ioctl */ -#define MTIP_PF_EH_ACTIVE_BIT 1 /* error handling */ -#define MTIP_PF_SE_ACTIVE_BIT 2 /* secure erase */ -#define MTIP_PF_DM_ACTIVE_BIT 3 /* download microcde */ -#define MTIP_PF_PAUSE_IO ((1 << MTIP_PF_IC_ACTIVE_BIT) | \ +enum { + /* below are bit numbers in 'flags' defined in mtip_port */ + MTIP_PF_IC_ACTIVE_BIT = 0, /* pio/ioctl */ + MTIP_PF_EH_ACTIVE_BIT = 1, /* error handling */ + MTIP_PF_SE_ACTIVE_BIT = 2, /* secure erase */ + MTIP_PF_DM_ACTIVE_BIT = 3, /* download microcde */ + MTIP_PF_PAUSE_IO = ((1 << MTIP_PF_IC_ACTIVE_BIT) | \ (1 << MTIP_PF_EH_ACTIVE_BIT) | \ (1 << MTIP_PF_SE_ACTIVE_BIT) | \ - (1 << MTIP_PF_DM_ACTIVE_BIT)) - -#define MTIP_PF_SVC_THD_ACTIVE_BIT 4 -#define MTIP_PF_ISSUE_CMDS_BIT 5 -#define MTIP_PF_REBUILD_BIT 6 -#define MTIP_PF_SVC_THD_STOP_BIT 8 - -/* below are bit numbers in 'dd_flag' defined in driver_data */ -#define MTIP_DDF_REMOVE_PENDING_BIT 1 -#define MTIP_DDF_OVER_TEMP_BIT 2 -#define MTIP_DDF_WRITE_PROTECT_BIT 3 -#define MTIP_DDF_STOP_IO ((1 << MTIP_DDF_REMOVE_PENDING_BIT) | \ + (1 << MTIP_PF_DM_ACTIVE_BIT)), + + MTIP_PF_SVC_THD_ACTIVE_BIT = 4, + MTIP_PF_ISSUE_CMDS_BIT = 5, + MTIP_PF_REBUILD_BIT = 6, + MTIP_PF_SVC_THD_STOP_BIT = 8, + + /* below are bit numbers in 'dd_flag' defined in driver_data */ + MTIP_DDF_REMOVE_PENDING_BIT = 1, + MTIP_DDF_OVER_TEMP_BIT = 2, + MTIP_DDF_WRITE_PROTECT_BIT = 3, + MTIP_DDF_STOP_IO = ((1 << MTIP_DDF_REMOVE_PENDING_BIT) | \ (1 << MTIP_DDF_OVER_TEMP_BIT) | \ - (1 << MTIP_DDF_WRITE_PROTECT_BIT)) + (1 << MTIP_DDF_WRITE_PROTECT_BIT)), -#define MTIP_DDF_CLEANUP_BIT 5 -#define MTIP_DDF_RESUME_BIT 6 -#define MTIP_DDF_INIT_DONE_BIT 7 -#define MTIP_DDF_REBUILD_FAILED_BIT 8 + MTIP_DDF_CLEANUP_BIT = 5, + MTIP_DDF_RESUME_BIT = 6, + MTIP_DDF_INIT_DONE_BIT = 7, + MTIP_DDF_REBUILD_FAILED_BIT = 8, +}; __packed struct smart_attr{ u8 attr_id; diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index 013c7a549fb..65665c9c42c 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -141,7 +141,7 @@ struct rbd_request { struct rbd_snap { struct device dev; const char *name; - size_t size; + u64 size; struct list_head node; u64 id; }; @@ -175,8 +175,7 @@ struct rbd_device { /* protects updating the header */ struct rw_semaphore header_rwsem; char snap_name[RBD_MAX_SNAP_NAME_LEN]; - u32 cur_snap; /* index+1 of current snapshot within snap context - 0 - for the head */ + u64 snap_id; /* current snapshot id */ int read_only; struct list_head node; @@ -241,7 +240,7 @@ static void rbd_put_dev(struct rbd_device *rbd_dev) put_device(&rbd_dev->dev); } -static int __rbd_update_snaps(struct rbd_device *rbd_dev); +static int __rbd_refresh_header(struct rbd_device *rbd_dev); static int rbd_open(struct block_device *bdev, fmode_t mode) { @@ -450,7 +449,9 @@ static void rbd_client_release(struct kref *kref) struct rbd_client *rbdc = container_of(kref, struct rbd_client, kref); dout("rbd_release_client %p\n", rbdc); + spin_lock(&rbd_client_list_lock); list_del(&rbdc->node); + spin_unlock(&rbd_client_list_lock); ceph_destroy_client(rbdc->client); kfree(rbdc->rbd_opts); @@ -463,9 +464,7 @@ static void rbd_client_release(struct kref *kref) */ static void rbd_put_client(struct rbd_device *rbd_dev) { - spin_lock(&rbd_client_list_lock); kref_put(&rbd_dev->rbd_client->kref, rbd_client_release); - spin_unlock(&rbd_client_list_lock); rbd_dev->rbd_client = NULL; } @@ -487,16 +486,18 @@ static void rbd_coll_release(struct kref *kref) */ static int rbd_header_from_disk(struct rbd_image_header *header, struct rbd_image_header_ondisk *ondisk, - int allocated_snaps, + u32 allocated_snaps, gfp_t gfp_flags) { - int i; - u32 snap_count; + u32 i, snap_count; if (memcmp(ondisk, RBD_HEADER_TEXT, sizeof(RBD_HEADER_TEXT))) return -ENXIO; snap_count = le32_to_cpu(ondisk->snap_count); + if (snap_count > (UINT_MAX - sizeof(struct ceph_snap_context)) + / sizeof (*ondisk)) + return -EINVAL; header->snapc = kmalloc(sizeof(struct ceph_snap_context) + snap_count * sizeof (*ondisk), gfp_flags); @@ -506,11 +507,11 @@ static int rbd_header_from_disk(struct rbd_image_header *header, header->snap_names_len = le64_to_cpu(ondisk->snap_names_len); if (snap_count) { header->snap_names = kmalloc(header->snap_names_len, - GFP_KERNEL); + gfp_flags); if (!header->snap_names) goto err_snapc; header->snap_sizes = kmalloc(snap_count * sizeof(u64), - GFP_KERNEL); + gfp_flags); if (!header->snap_sizes) goto err_names; } else { @@ -552,21 +553,6 @@ err_snapc: return -ENOMEM; } -static int snap_index(struct rbd_image_header *header, int snap_num) -{ - return header->total_snaps - snap_num; -} - -static u64 cur_snap_id(struct rbd_device *rbd_dev) -{ - struct rbd_image_header *header = &rbd_dev->header; - - if (!rbd_dev->cur_snap) - return 0; - - return header->snapc->snaps[snap_index(header, rbd_dev->cur_snap)]; -} - static int snap_by_name(struct rbd_image_header *header, const char *snap_name, u64 *seq, u64 *size) { @@ -605,7 +591,7 @@ static int rbd_header_set_snap(struct rbd_device *dev, u64 *size) snapc->seq = header->snap_seq; else snapc->seq = 0; - dev->cur_snap = 0; + dev->snap_id = CEPH_NOSNAP; dev->read_only = 0; if (size) *size = header->image_size; @@ -613,8 +599,7 @@ static int rbd_header_set_snap(struct rbd_device *dev, u64 *size) ret = snap_by_name(header, dev->snap_name, &snapc->seq, size); if (ret < 0) goto done; - - dev->cur_snap = header->total_snaps - ret; + dev->snap_id = snapc->seq; dev->read_only = 1; } @@ -935,7 +920,6 @@ static int rbd_do_request(struct request *rq, layout->fl_stripe_unit = cpu_to_le32(1 << RBD_MAX_OBJ_ORDER); layout->fl_stripe_count = cpu_to_le32(1); layout->fl_object_size = cpu_to_le32(1 << RBD_MAX_OBJ_ORDER); - layout->fl_pg_preferred = cpu_to_le32(-1); layout->fl_pg_pool = cpu_to_le32(dev->poolid); ceph_calc_raw_layout(osdc, layout, snapid, ofs, &len, &bno, req, ops); @@ -1168,7 +1152,7 @@ static int rbd_req_read(struct request *rq, int coll_index) { return rbd_do_op(rq, rbd_dev, NULL, - (snapid ? snapid : CEPH_NOSNAP), + snapid, CEPH_OSD_OP_READ, CEPH_OSD_FLAG_READ, 2, @@ -1187,7 +1171,7 @@ static int rbd_req_sync_read(struct rbd_device *dev, u64 *ver) { return rbd_req_sync_op(dev, NULL, - (snapid ? snapid : CEPH_NOSNAP), + snapid, CEPH_OSD_OP_READ, CEPH_OSD_FLAG_READ, NULL, @@ -1238,7 +1222,7 @@ static void rbd_watch_cb(u64 ver, u64 notify_id, u8 opcode, void *data) dout("rbd_watch_cb %s notify_id=%lld opcode=%d\n", dev->obj_md_name, notify_id, (int)opcode); mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); - rc = __rbd_update_snaps(dev); + rc = __rbd_refresh_header(dev); mutex_unlock(&ctl_mutex); if (rc) pr_warning(RBD_DRV_NAME "%d got notification but failed to " @@ -1521,7 +1505,7 @@ static void rbd_rq_fn(struct request_queue *q) coll, cur_seg); else rbd_req_read(rq, rbd_dev, - cur_snap_id(rbd_dev), + rbd_dev->snap_id, ofs, op_size, bio, coll, cur_seg); @@ -1592,7 +1576,7 @@ static int rbd_read_header(struct rbd_device *rbd_dev, { ssize_t rc; struct rbd_image_header_ondisk *dh; - int snap_count = 0; + u32 snap_count = 0; u64 ver; size_t len; @@ -1656,7 +1640,7 @@ static int rbd_header_add_snap(struct rbd_device *dev, struct ceph_mon_client *monc; /* we should create a snapshot only if we're pointing at the head */ - if (dev->cur_snap) + if (dev->snap_id != CEPH_NOSNAP) return -EINVAL; monc = &dev->rbd_client->client->monc; @@ -1683,7 +1667,9 @@ static int rbd_header_add_snap(struct rbd_device *dev, if (ret < 0) return ret; - dev->header.snapc->seq = new_snapid; + down_write(&dev->header_rwsem); + dev->header.snapc->seq = new_snapid; + up_write(&dev->header_rwsem); return 0; bad: @@ -1703,7 +1689,7 @@ static void __rbd_remove_all_snaps(struct rbd_device *rbd_dev) /* * only read the first part of the ondisk header, without the snaps info */ -static int __rbd_update_snaps(struct rbd_device *rbd_dev) +static int __rbd_refresh_header(struct rbd_device *rbd_dev) { int ret; struct rbd_image_header h; @@ -1890,7 +1876,7 @@ static ssize_t rbd_image_refresh(struct device *dev, mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); - rc = __rbd_update_snaps(rbd_dev); + rc = __rbd_refresh_header(rbd_dev); if (rc < 0) ret = rc; @@ -1949,7 +1935,7 @@ static ssize_t rbd_snap_size_show(struct device *dev, { struct rbd_snap *snap = container_of(dev, struct rbd_snap, dev); - return sprintf(buf, "%zd\n", snap->size); + return sprintf(buf, "%llu\n", (unsigned long long)snap->size); } static ssize_t rbd_snap_id_show(struct device *dev, @@ -1958,7 +1944,7 @@ static ssize_t rbd_snap_id_show(struct device *dev, { struct rbd_snap *snap = container_of(dev, struct rbd_snap, dev); - return sprintf(buf, "%llu\n", (unsigned long long) snap->id); + return sprintf(buf, "%llu\n", (unsigned long long)snap->id); } static DEVICE_ATTR(snap_size, S_IRUGO, rbd_snap_size_show, NULL); @@ -2173,7 +2159,7 @@ static int rbd_init_watch_dev(struct rbd_device *rbd_dev) rbd_dev->header.obj_version); if (ret == -ERANGE) { mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); - rc = __rbd_update_snaps(rbd_dev); + rc = __rbd_refresh_header(rbd_dev); mutex_unlock(&ctl_mutex); if (rc < 0) return rc; @@ -2558,7 +2544,7 @@ static ssize_t rbd_snap_add(struct device *dev, if (ret < 0) goto err_unlock; - ret = __rbd_update_snaps(rbd_dev); + ret = __rbd_refresh_header(rbd_dev); if (ret < 0) goto err_unlock; diff --git a/drivers/block/xen-blkfront.c b/drivers/block/xen-blkfront.c index 4e86393a09c..60eed4bdd2e 100644 --- a/drivers/block/xen-blkfront.c +++ b/drivers/block/xen-blkfront.c @@ -526,6 +526,14 @@ static int xen_translate_vdev(int vdevice, int *minor, unsigned int *offset) return 0; } +static char *encode_disk_name(char *ptr, unsigned int n) +{ + if (n >= 26) + ptr = encode_disk_name(ptr, n / 26 - 1); + *ptr = 'a' + n % 26; + return ptr + 1; +} + static int xlvbd_alloc_gendisk(blkif_sector_t capacity, struct blkfront_info *info, u16 vdisk_info, u16 sector_size) @@ -536,6 +544,7 @@ static int xlvbd_alloc_gendisk(blkif_sector_t capacity, unsigned int offset; int minor; int nr_parts; + char *ptr; BUG_ON(info->gd != NULL); BUG_ON(info->rq != NULL); @@ -560,7 +569,11 @@ static int xlvbd_alloc_gendisk(blkif_sector_t capacity, "emulated IDE disks,\n\t choose an xvd device name" "from xvde on\n", info->vdevice); } - err = -ENODEV; + if (minor >> MINORBITS) { + pr_warn("blkfront: %#x's minor (%#x) out of range; ignoring\n", + info->vdevice, minor); + return -ENODEV; + } if ((minor % nr_parts) == 0) nr_minors = nr_parts; @@ -574,23 +587,14 @@ static int xlvbd_alloc_gendisk(blkif_sector_t capacity, if (gd == NULL) goto release; - if (nr_minors > 1) { - if (offset < 26) - sprintf(gd->disk_name, "%s%c", DEV_NAME, 'a' + offset); - else - sprintf(gd->disk_name, "%s%c%c", DEV_NAME, - 'a' + ((offset / 26)-1), 'a' + (offset % 26)); - } else { - if (offset < 26) - sprintf(gd->disk_name, "%s%c%d", DEV_NAME, - 'a' + offset, - minor & (nr_parts - 1)); - else - sprintf(gd->disk_name, "%s%c%c%d", DEV_NAME, - 'a' + ((offset / 26) - 1), - 'a' + (offset % 26), - minor & (nr_parts - 1)); - } + strcpy(gd->disk_name, DEV_NAME); + ptr = encode_disk_name(gd->disk_name + sizeof(DEV_NAME) - 1, offset); + BUG_ON(ptr >= gd->disk_name + DISK_NAME_LEN); + if (nr_minors > 1) + *ptr = 0; + else + snprintf(ptr, gd->disk_name + DISK_NAME_LEN - ptr, + "%d", minor & (nr_parts - 1)); gd->major = XENVBD_MAJOR; gd->first_minor = minor; @@ -1496,7 +1500,9 @@ module_init(xlblk_init); static void __exit xlblk_exit(void) { - return xenbus_unregister_driver(&blkfront_driver); + xenbus_unregister_driver(&blkfront_driver); + unregister_blkdev(XENVBD_MAJOR, DEV_NAME); + kfree(minors); } module_exit(xlblk_exit); diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index 7ef73c919c5..7be9b7288e9 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -715,25 +715,6 @@ static inline u64 input_addr_to_sys_addr(struct mem_ctl_info *mci, input_addr_to_dram_addr(mci, input_addr)); } -/* - * Find the minimum and maximum InputAddr values that map to the given @csrow. - * Pass back these values in *input_addr_min and *input_addr_max. - */ -static void find_csrow_limits(struct mem_ctl_info *mci, int csrow, - u64 *input_addr_min, u64 *input_addr_max) -{ - struct amd64_pvt *pvt; - u64 base, mask; - - pvt = mci->pvt_info; - BUG_ON((csrow < 0) || (csrow >= pvt->csels[0].b_cnt)); - - get_cs_base_and_mask(pvt, csrow, 0, &base, &mask); - - *input_addr_min = base & ~mask; - *input_addr_max = base | mask; -} - /* Map the Error address to a PAGE and PAGE OFFSET. */ static inline void error_address_to_page_and_offset(u64 error_address, u32 *page, u32 *offset) @@ -1058,6 +1039,37 @@ static void k8_map_sysaddr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr, int channel, csrow; u32 page, offset; + error_address_to_page_and_offset(sys_addr, &page, &offset); + + /* + * Find out which node the error address belongs to. This may be + * different from the node that detected the error. + */ + src_mci = find_mc_by_sys_addr(mci, sys_addr); + if (!src_mci) { + amd64_mc_err(mci, "failed to map error addr 0x%lx to a node\n", + (unsigned long)sys_addr); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offset, syndrome, + -1, -1, -1, + EDAC_MOD_STR, + "failed to map error addr to a node", + NULL); + return; + } + + /* Now map the sys_addr to a CSROW */ + csrow = sys_addr_to_csrow(src_mci, sys_addr); + if (csrow < 0) { + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offset, syndrome, + -1, -1, -1, + EDAC_MOD_STR, + "failed to map error addr to a csrow", + NULL); + return; + } + /* CHIPKILL enabled */ if (pvt->nbcfg & NBCFG_CHIPKILL) { channel = get_channel_from_ecc_syndrome(mci, syndrome); @@ -1067,9 +1079,15 @@ static void k8_map_sysaddr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr, * 2 DIMMs is in error. So we need to ID 'both' of them * as suspect. */ - amd64_mc_warn(mci, "unknown syndrome 0x%04x - possible " - "error reporting race\n", syndrome); - edac_mc_handle_ce_no_info(mci, EDAC_MOD_STR); + amd64_mc_warn(src_mci, "unknown syndrome 0x%04x - " + "possible error reporting race\n", + syndrome); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offset, syndrome, + csrow, -1, -1, + EDAC_MOD_STR, + "unknown syndrome - possible error reporting race", + NULL); return; } } else { @@ -1084,28 +1102,10 @@ static void k8_map_sysaddr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr, channel = ((sys_addr & BIT(3)) != 0); } - /* - * Find out which node the error address belongs to. This may be - * different from the node that detected the error. - */ - src_mci = find_mc_by_sys_addr(mci, sys_addr); - if (!src_mci) { - amd64_mc_err(mci, "failed to map error addr 0x%lx to a node\n", - (unsigned long)sys_addr); - edac_mc_handle_ce_no_info(mci, EDAC_MOD_STR); - return; - } - - /* Now map the sys_addr to a CSROW */ - csrow = sys_addr_to_csrow(src_mci, sys_addr); - if (csrow < 0) { - edac_mc_handle_ce_no_info(src_mci, EDAC_MOD_STR); - } else { - error_address_to_page_and_offset(sys_addr, &page, &offset); - - edac_mc_handle_ce(src_mci, page, offset, syndrome, csrow, - channel, EDAC_MOD_STR); - } + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, src_mci, + page, offset, syndrome, + csrow, channel, -1, + EDAC_MOD_STR, "", NULL); } static int ddr2_cs_size(unsigned i, bool dct_width) @@ -1611,15 +1611,20 @@ static void f1x_map_sysaddr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr, u32 page, offset; int nid, csrow, chan = 0; + error_address_to_page_and_offset(sys_addr, &page, &offset); + csrow = f1x_translate_sysaddr_to_cs(pvt, sys_addr, &nid, &chan); if (csrow < 0) { - edac_mc_handle_ce_no_info(mci, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offset, syndrome, + -1, -1, -1, + EDAC_MOD_STR, + "failed to map error addr to a csrow", + NULL); return; } - error_address_to_page_and_offset(sys_addr, &page, &offset); - /* * We need the syndromes for channel detection only when we're * ganged. Otherwise @chan should already contain the channel at @@ -1628,16 +1633,10 @@ static void f1x_map_sysaddr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr, if (dct_ganging_enabled(pvt)) chan = get_channel_from_ecc_syndrome(mci, syndrome); - if (chan >= 0) - edac_mc_handle_ce(mci, page, offset, syndrome, csrow, chan, - EDAC_MOD_STR); - else - /* - * Channel unknown, report all channels on this CSROW as failed. - */ - for (chan = 0; chan < mci->csrows[csrow].nr_channels; chan++) - edac_mc_handle_ce(mci, page, offset, syndrome, - csrow, chan, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offset, syndrome, + csrow, chan, -1, + EDAC_MOD_STR, "", NULL); } /* @@ -1918,7 +1917,12 @@ static void amd64_handle_ce(struct mem_ctl_info *mci, struct mce *m) /* Ensure that the Error Address is VALID */ if (!(m->status & MCI_STATUS_ADDRV)) { amd64_mc_err(mci, "HW has no ERROR_ADDRESS available\n"); - edac_mc_handle_ce_no_info(mci, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + 0, 0, 0, + -1, -1, -1, + EDAC_MOD_STR, + "HW has no ERROR_ADDRESS available", + NULL); return; } @@ -1942,11 +1946,17 @@ static void amd64_handle_ue(struct mem_ctl_info *mci, struct mce *m) if (!(m->status & MCI_STATUS_ADDRV)) { amd64_mc_err(mci, "HW has no ERROR_ADDRESS available\n"); - edac_mc_handle_ue_no_info(log_mci, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + 0, 0, 0, + -1, -1, -1, + EDAC_MOD_STR, + "HW has no ERROR_ADDRESS available", + NULL); return; } sys_addr = get_error_address(m); + error_address_to_page_and_offset(sys_addr, &page, &offset); /* * Find out which node the error address belongs to. This may be @@ -1956,7 +1966,11 @@ static void amd64_handle_ue(struct mem_ctl_info *mci, struct mce *m) if (!src_mci) { amd64_mc_err(mci, "ERROR ADDRESS (0x%lx) NOT mapped to a MC\n", (unsigned long)sys_addr); - edac_mc_handle_ue_no_info(log_mci, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, offset, 0, + -1, -1, -1, + EDAC_MOD_STR, + "ERROR ADDRESS NOT mapped to a MC", NULL); return; } @@ -1966,10 +1980,17 @@ static void amd64_handle_ue(struct mem_ctl_info *mci, struct mce *m) if (csrow < 0) { amd64_mc_err(mci, "ERROR_ADDRESS (0x%lx) NOT mapped to CS\n", (unsigned long)sys_addr); - edac_mc_handle_ue_no_info(log_mci, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, offset, 0, + -1, -1, -1, + EDAC_MOD_STR, + "ERROR ADDRESS NOT mapped to CS", + NULL); } else { - error_address_to_page_and_offset(sys_addr, &page, &offset); - edac_mc_handle_ue(log_mci, page, offset, csrow, EDAC_MOD_STR); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, offset, 0, + csrow, -1, -1, + EDAC_MOD_STR, "", NULL); } } @@ -2171,7 +2192,7 @@ static u32 amd64_csrow_nr_pages(struct amd64_pvt *pvt, u8 dct, int csrow_nr) nr_pages = pvt->ops->dbam_to_cs(pvt, dct, cs_mode) << (20 - PAGE_SHIFT); debugf0(" (csrow=%d) DBAM map index= %d\n", csrow_nr, cs_mode); - debugf0(" nr_pages= %u channel-count = %d\n", + debugf0(" nr_pages/channel= %u channel-count = %d\n", nr_pages, pvt->channel_count); return nr_pages; @@ -2185,9 +2206,12 @@ static int init_csrows(struct mem_ctl_info *mci) { struct csrow_info *csrow; struct amd64_pvt *pvt = mci->pvt_info; - u64 input_addr_min, input_addr_max, sys_addr, base, mask; + u64 base, mask; u32 val; - int i, empty = 1; + int i, j, empty = 1; + enum mem_type mtype; + enum edac_type edac_mode; + int nr_pages = 0; amd64_read_pci_cfg(pvt->F3, NBCFG, &val); @@ -2211,41 +2235,32 @@ static int init_csrows(struct mem_ctl_info *mci) empty = 0; if (csrow_enabled(i, 0, pvt)) - csrow->nr_pages = amd64_csrow_nr_pages(pvt, 0, i); + nr_pages = amd64_csrow_nr_pages(pvt, 0, i); if (csrow_enabled(i, 1, pvt)) - csrow->nr_pages += amd64_csrow_nr_pages(pvt, 1, i); - find_csrow_limits(mci, i, &input_addr_min, &input_addr_max); - sys_addr = input_addr_to_sys_addr(mci, input_addr_min); - csrow->first_page = (u32) (sys_addr >> PAGE_SHIFT); - sys_addr = input_addr_to_sys_addr(mci, input_addr_max); - csrow->last_page = (u32) (sys_addr >> PAGE_SHIFT); + nr_pages += amd64_csrow_nr_pages(pvt, 1, i); get_cs_base_and_mask(pvt, i, 0, &base, &mask); - csrow->page_mask = ~mask; /* 8 bytes of resolution */ - csrow->mtype = amd64_determine_memory_type(pvt, i); + mtype = amd64_determine_memory_type(pvt, i); debugf1(" for MC node %d csrow %d:\n", pvt->mc_node_id, i); - debugf1(" input_addr_min: 0x%lx input_addr_max: 0x%lx\n", - (unsigned long)input_addr_min, - (unsigned long)input_addr_max); - debugf1(" sys_addr: 0x%lx page_mask: 0x%lx\n", - (unsigned long)sys_addr, csrow->page_mask); - debugf1(" nr_pages: %u first_page: 0x%lx " - "last_page: 0x%lx\n", - (unsigned)csrow->nr_pages, - csrow->first_page, csrow->last_page); + debugf1(" nr_pages: %u\n", nr_pages * pvt->channel_count); /* * determine whether CHIPKILL or JUST ECC or NO ECC is operating */ if (pvt->nbcfg & NBCFG_ECC_ENABLE) - csrow->edac_mode = - (pvt->nbcfg & NBCFG_CHIPKILL) ? - EDAC_S4ECD4ED : EDAC_SECDED; + edac_mode = (pvt->nbcfg & NBCFG_CHIPKILL) ? + EDAC_S4ECD4ED : EDAC_SECDED; else - csrow->edac_mode = EDAC_NONE; + edac_mode = EDAC_NONE; + + for (j = 0; j < pvt->channel_count; j++) { + csrow->channels[j].dimm->mtype = mtype; + csrow->channels[j].dimm->edac_mode = edac_mode; + csrow->channels[j].dimm->nr_pages = nr_pages; + } } return empty; @@ -2540,6 +2555,7 @@ static int amd64_init_one_instance(struct pci_dev *F2) struct amd64_pvt *pvt = NULL; struct amd64_family_type *fam_type = NULL; struct mem_ctl_info *mci = NULL; + struct edac_mc_layer layers[2]; int err = 0, ret; u8 nid = get_node_id(F2); @@ -2574,7 +2590,13 @@ static int amd64_init_one_instance(struct pci_dev *F2) goto err_siblings; ret = -ENOMEM; - mci = edac_mc_alloc(0, pvt->csels[0].b_cnt, pvt->channel_count, nid); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = pvt->csels[0].b_cnt; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = pvt->channel_count; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(nid, ARRAY_SIZE(layers), layers, 0); if (!mci) goto err_siblings; diff --git a/drivers/edac/amd76x_edac.c b/drivers/edac/amd76x_edac.c index f8fd3c807bd..9774d443fa5 100644 --- a/drivers/edac/amd76x_edac.c +++ b/drivers/edac/amd76x_edac.c @@ -29,7 +29,6 @@ edac_mc_chipset_printk(mci, level, "amd76x", fmt, ##arg) #define AMD76X_NR_CSROWS 8 -#define AMD76X_NR_CHANS 1 #define AMD76X_NR_DIMMS 4 /* AMD 76x register addresses - device 0 function 0 - PCI bridge */ @@ -146,8 +145,10 @@ static int amd76x_process_error_info(struct mem_ctl_info *mci, if (handle_errors) { row = (info->ecc_mode_status >> 4) & 0xf; - edac_mc_handle_ue(mci, mci->csrows[row].first_page, 0, - row, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + mci->csrows[row].first_page, 0, 0, + row, 0, -1, + mci->ctl_name, "", NULL); } } @@ -159,8 +160,10 @@ static int amd76x_process_error_info(struct mem_ctl_info *mci, if (handle_errors) { row = info->ecc_mode_status & 0xf; - edac_mc_handle_ce(mci, mci->csrows[row].first_page, 0, - 0, row, 0, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + mci->csrows[row].first_page, 0, 0, + row, 0, -1, + mci->ctl_name, "", NULL); } } @@ -186,11 +189,13 @@ static void amd76x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, enum edac_type edac_mode) { struct csrow_info *csrow; + struct dimm_info *dimm; u32 mba, mba_base, mba_mask, dms; int index; for (index = 0; index < mci->nr_csrows; index++) { csrow = &mci->csrows[index]; + dimm = csrow->channels[0].dimm; /* find the DRAM Chip Select Base address and mask */ pci_read_config_dword(pdev, @@ -203,13 +208,13 @@ static void amd76x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, mba_mask = ((mba & 0xff80) << 16) | 0x7fffffUL; pci_read_config_dword(pdev, AMD76X_DRAM_MODE_STATUS, &dms); csrow->first_page = mba_base >> PAGE_SHIFT; - csrow->nr_pages = (mba_mask + 1) >> PAGE_SHIFT; - csrow->last_page = csrow->first_page + csrow->nr_pages - 1; + dimm->nr_pages = (mba_mask + 1) >> PAGE_SHIFT; + csrow->last_page = csrow->first_page + dimm->nr_pages - 1; csrow->page_mask = mba_mask >> PAGE_SHIFT; - csrow->grain = csrow->nr_pages << PAGE_SHIFT; - csrow->mtype = MEM_RDDR; - csrow->dtype = ((dms >> index) & 0x1) ? DEV_X4 : DEV_UNKNOWN; - csrow->edac_mode = edac_mode; + dimm->grain = dimm->nr_pages << PAGE_SHIFT; + dimm->mtype = MEM_RDDR; + dimm->dtype = ((dms >> index) & 0x1) ? DEV_X4 : DEV_UNKNOWN; + dimm->edac_mode = edac_mode; } } @@ -230,7 +235,8 @@ static int amd76x_probe1(struct pci_dev *pdev, int dev_idx) EDAC_SECDED, EDAC_SECDED }; - struct mem_ctl_info *mci = NULL; + struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; u32 ems; u32 ems_mode; struct amd76x_error_info discard; @@ -238,11 +244,17 @@ static int amd76x_probe1(struct pci_dev *pdev, int dev_idx) debugf0("%s()\n", __func__); pci_read_config_dword(pdev, AMD76X_ECC_MODE_STATUS, &ems); ems_mode = (ems >> 10) & 0x3; - mci = edac_mc_alloc(0, AMD76X_NR_CSROWS, AMD76X_NR_CHANS, 0); - if (mci == NULL) { + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = AMD76X_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = 1; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); + + if (mci == NULL) return -ENOMEM; - } debugf0("%s(): mci = %p\n", __func__, mci); mci->dev = &pdev->dev; diff --git a/drivers/edac/cell_edac.c b/drivers/edac/cell_edac.c index 9a6a274e692..69ee6aab5c7 100644 --- a/drivers/edac/cell_edac.c +++ b/drivers/edac/cell_edac.c @@ -48,8 +48,9 @@ static void cell_edac_count_ce(struct mem_ctl_info *mci, int chan, u64 ar) syndrome = (ar & 0x000000001fe00000ul) >> 21; /* TODO: Decoding of the error address */ - edac_mc_handle_ce(mci, csrow->first_page + pfn, offset, - syndrome, 0, chan, ""); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + csrow->first_page + pfn, offset, syndrome, + 0, chan, -1, "", "", NULL); } static void cell_edac_count_ue(struct mem_ctl_info *mci, int chan, u64 ar) @@ -69,7 +70,9 @@ static void cell_edac_count_ue(struct mem_ctl_info *mci, int chan, u64 ar) offset = address & ~PAGE_MASK; /* TODO: Decoding of the error address */ - edac_mc_handle_ue(mci, csrow->first_page + pfn, offset, 0, ""); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + csrow->first_page + pfn, offset, 0, + 0, chan, -1, "", "", NULL); } static void cell_edac_check(struct mem_ctl_info *mci) @@ -124,8 +127,11 @@ static void cell_edac_check(struct mem_ctl_info *mci) static void __devinit cell_edac_init_csrows(struct mem_ctl_info *mci) { struct csrow_info *csrow = &mci->csrows[0]; + struct dimm_info *dimm; struct cell_edac_priv *priv = mci->pvt_info; struct device_node *np; + int j; + u32 nr_pages; for (np = NULL; (np = of_find_node_by_name(np, "memory")) != NULL;) { @@ -140,15 +146,20 @@ static void __devinit cell_edac_init_csrows(struct mem_ctl_info *mci) if (of_node_to_nid(np) != priv->node) continue; csrow->first_page = r.start >> PAGE_SHIFT; - csrow->nr_pages = resource_size(&r) >> PAGE_SHIFT; - csrow->last_page = csrow->first_page + csrow->nr_pages - 1; - csrow->mtype = MEM_XDR; - csrow->edac_mode = EDAC_SECDED; + nr_pages = resource_size(&r) >> PAGE_SHIFT; + csrow->last_page = csrow->first_page + nr_pages - 1; + + for (j = 0; j < csrow->nr_channels; j++) { + dimm = csrow->channels[j].dimm; + dimm->mtype = MEM_XDR; + dimm->edac_mode = EDAC_SECDED; + dimm->nr_pages = nr_pages / csrow->nr_channels; + } dev_dbg(mci->dev, "Initialized on node %d, chanmask=0x%x," " first_page=0x%lx, nr_pages=0x%x\n", priv->node, priv->chanmask, - csrow->first_page, csrow->nr_pages); + csrow->first_page, nr_pages); break; } } @@ -157,9 +168,10 @@ static int __devinit cell_edac_probe(struct platform_device *pdev) { struct cbe_mic_tm_regs __iomem *regs; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct cell_edac_priv *priv; u64 reg; - int rc, chanmask; + int rc, chanmask, num_chans; regs = cbe_get_cpu_mic_tm_regs(cbe_node_to_cpu(pdev->id)); if (regs == NULL) @@ -184,8 +196,16 @@ static int __devinit cell_edac_probe(struct platform_device *pdev) in_be64(®s->mic_fir)); /* Allocate & init EDAC MC data structure */ - mci = edac_mc_alloc(sizeof(struct cell_edac_priv), 1, - chanmask == 3 ? 2 : 1, pdev->id); + num_chans = chanmask == 3 ? 2 : 1; + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = 1; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = num_chans; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(pdev->id, ARRAY_SIZE(layers), layers, + sizeof(struct cell_edac_priv)); if (mci == NULL) return -ENOMEM; priv = mci->pvt_info; diff --git a/drivers/edac/cpc925_edac.c b/drivers/edac/cpc925_edac.c index a774c0ddaf5..e22030a9de6 100644 --- a/drivers/edac/cpc925_edac.c +++ b/drivers/edac/cpc925_edac.c @@ -329,9 +329,10 @@ static void cpc925_init_csrows(struct mem_ctl_info *mci) { struct cpc925_mc_pdata *pdata = mci->pvt_info; struct csrow_info *csrow; - int index; + struct dimm_info *dimm; + int index, j; u32 mbmr, mbbar, bba; - unsigned long row_size, last_nr_pages = 0; + unsigned long row_size, nr_pages, last_nr_pages = 0; get_total_mem(pdata); @@ -350,36 +351,41 @@ static void cpc925_init_csrows(struct mem_ctl_info *mci) row_size = bba * (1UL << 28); /* 256M */ csrow->first_page = last_nr_pages; - csrow->nr_pages = row_size >> PAGE_SHIFT; - csrow->last_page = csrow->first_page + csrow->nr_pages - 1; + nr_pages = row_size >> PAGE_SHIFT; + csrow->last_page = csrow->first_page + nr_pages - 1; last_nr_pages = csrow->last_page + 1; - csrow->mtype = MEM_RDDR; - csrow->edac_mode = EDAC_SECDED; - - switch (csrow->nr_channels) { - case 1: /* Single channel */ - csrow->grain = 32; /* four-beat burst of 32 bytes */ - break; - case 2: /* Dual channel */ - default: - csrow->grain = 64; /* four-beat burst of 64 bytes */ - break; - } - - switch ((mbmr & MBMR_MODE_MASK) >> MBMR_MODE_SHIFT) { - case 6: /* 0110, no way to differentiate X8 VS X16 */ - case 5: /* 0101 */ - case 8: /* 1000 */ - csrow->dtype = DEV_X16; - break; - case 7: /* 0111 */ - case 9: /* 1001 */ - csrow->dtype = DEV_X8; - break; - default: - csrow->dtype = DEV_UNKNOWN; - break; + for (j = 0; j < csrow->nr_channels; j++) { + dimm = csrow->channels[j].dimm; + + dimm->nr_pages = nr_pages / csrow->nr_channels; + dimm->mtype = MEM_RDDR; + dimm->edac_mode = EDAC_SECDED; + + switch (csrow->nr_channels) { + case 1: /* Single channel */ + dimm->grain = 32; /* four-beat burst of 32 bytes */ + break; + case 2: /* Dual channel */ + default: + dimm->grain = 64; /* four-beat burst of 64 bytes */ + break; + } + + switch ((mbmr & MBMR_MODE_MASK) >> MBMR_MODE_SHIFT) { + case 6: /* 0110, no way to differentiate X8 VS X16 */ + case 5: /* 0101 */ + case 8: /* 1000 */ + dimm->dtype = DEV_X16; + break; + case 7: /* 0111 */ + case 9: /* 1001 */ + dimm->dtype = DEV_X8; + break; + default: + dimm->dtype = DEV_UNKNOWN; + break; + } } } } @@ -549,13 +555,18 @@ static void cpc925_mc_check(struct mem_ctl_info *mci) if (apiexcp & CECC_EXCP_DETECTED) { cpc925_mc_printk(mci, KERN_INFO, "DRAM CECC Fault\n"); channel = cpc925_mc_find_channel(mci, syndrome); - edac_mc_handle_ce(mci, pfn, offset, syndrome, - csrow, channel, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + pfn, offset, syndrome, + csrow, channel, -1, + mci->ctl_name, "", NULL); } if (apiexcp & UECC_EXCP_DETECTED) { cpc925_mc_printk(mci, KERN_INFO, "DRAM UECC Fault\n"); - edac_mc_handle_ue(mci, pfn, offset, csrow, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + pfn, offset, 0, + csrow, -1, -1, + mci->ctl_name, "", NULL); } cpc925_mc_printk(mci, KERN_INFO, "Dump registers:\n"); @@ -927,6 +938,7 @@ static int __devinit cpc925_probe(struct platform_device *pdev) { static int edac_mc_idx; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; void __iomem *vbase; struct cpc925_mc_pdata *pdata; struct resource *r; @@ -962,9 +974,16 @@ static int __devinit cpc925_probe(struct platform_device *pdev) goto err2; } - nr_channels = cpc925_mc_get_channels(vbase); - mci = edac_mc_alloc(sizeof(struct cpc925_mc_pdata), - CPC925_NR_CSROWS, nr_channels + 1, edac_mc_idx); + nr_channels = cpc925_mc_get_channels(vbase) + 1; + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = CPC925_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = nr_channels; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(edac_mc_idx, ARRAY_SIZE(layers), layers, + sizeof(struct cpc925_mc_pdata)); if (!mci) { cpc925_printk(KERN_ERR, "No memory for mem_ctl_info\n"); res = -ENOMEM; diff --git a/drivers/edac/e752x_edac.c b/drivers/edac/e752x_edac.c index 41223261ede..3186512c973 100644 --- a/drivers/edac/e752x_edac.c +++ b/drivers/edac/e752x_edac.c @@ -4,7 +4,11 @@ * This file may be distributed under the terms of the * GNU General Public License. * - * See "enum e752x_chips" below for supported chipsets + * Implement support for the e7520, E7525, e7320 and i3100 memory controllers. + * + * Datasheets: + * http://www.intel.in/content/www/in/en/chipsets/e7525-memory-controller-hub-datasheet.html + * ftp://download.intel.com/design/intarch/datashts/31345803.pdf * * Written by Tom Zimmerman * @@ -13,8 +17,6 @@ * Wang Zhenyu at intel.com * Dave Jiang at mvista.com * - * $Id: edac_e752x.c,v 1.5.2.11 2005/10/05 00:43:44 dsp_llnl Exp $ - * */ #include <linux/module.h> @@ -187,6 +189,25 @@ enum e752x_chips { I3100 = 3 }; +/* + * Those chips Support single-rank and dual-rank memories only. + * + * On e752x chips, the odd rows are present only on dual-rank memories. + * Dividing the rank by two will provide the dimm# + * + * i3100 MC has a different mapping: it supports only 4 ranks. + * + * The mapping is (from 1 to n): + * slot single-ranked double-ranked + * dimm #1 -> rank #4 NA + * dimm #2 -> rank #3 NA + * dimm #3 -> rank #2 Ranks 2 and 3 + * dimm #4 -> rank $1 Ranks 1 and 4 + * + * FIXME: The current mapping for i3100 considers that it supports up to 8 + * ranks/chanel, but datasheet says that the MC supports only 4 ranks. + */ + struct e752x_pvt { struct pci_dev *bridge_ck; struct pci_dev *dev_d0f0; @@ -350,8 +371,10 @@ static void do_process_ce(struct mem_ctl_info *mci, u16 error_one, channel = !(error_one & 1); /* e752x mc reads 34:6 of the DRAM linear address */ - edac_mc_handle_ce(mci, page, offset_in_page(sec1_add << 4), - sec1_syndrome, row, channel, "e752x CE"); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offset_in_page(sec1_add << 4), sec1_syndrome, + row, channel, -1, + "e752x CE", "", NULL); } static inline void process_ce(struct mem_ctl_info *mci, u16 error_one, @@ -385,9 +408,12 @@ static void do_process_ue(struct mem_ctl_info *mci, u16 error_one, edac_mc_find_csrow_by_page(mci, block_page); /* e752x mc reads 34:6 of the DRAM linear address */ - edac_mc_handle_ue(mci, block_page, - offset_in_page(error_2b << 4), - row, "e752x UE from Read"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + block_page, + offset_in_page(error_2b << 4), 0, + row, -1, -1, + "e752x UE from Read", "", NULL); + } if (error_one & 0x0404) { error_2b = scrb_add; @@ -401,9 +427,11 @@ static void do_process_ue(struct mem_ctl_info *mci, u16 error_one, edac_mc_find_csrow_by_page(mci, block_page); /* e752x mc reads 34:6 of the DRAM linear address */ - edac_mc_handle_ue(mci, block_page, - offset_in_page(error_2b << 4), - row, "e752x UE from Scruber"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + block_page, + offset_in_page(error_2b << 4), 0, + row, -1, -1, + "e752x UE from Scruber", "", NULL); } } @@ -426,7 +454,9 @@ static inline void process_ue_no_info_wr(struct mem_ctl_info *mci, return; debugf3("%s()\n", __func__); - edac_mc_handle_ue_no_info(mci, "e752x UE log memory write"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, + "e752x UE log memory write", "", NULL); } static void do_process_ded_retry(struct mem_ctl_info *mci, u16 error, @@ -1044,7 +1074,7 @@ static void e752x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, int drc_drbg; /* DRB granularity 0=64mb, 1=128mb */ int drc_ddim; /* DRAM Data Integrity Mode 0=none, 2=edac */ u8 value; - u32 dra, drc, cumul_size; + u32 dra, drc, cumul_size, i, nr_pages; dra = 0; for (index = 0; index < 4; index++) { @@ -1053,7 +1083,7 @@ static void e752x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, dra |= dra_reg << (index * 8); } pci_read_config_dword(pdev, E752X_DRC, &drc); - drc_chan = dual_channel_active(ddrcsr); + drc_chan = dual_channel_active(ddrcsr) ? 1 : 0; drc_drbg = drc_chan + 1; /* 128 in dual mode, 64 in single */ drc_ddim = (drc >> 20) & 0x3; @@ -1078,26 +1108,33 @@ static void e752x_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, csrow->first_page = last_cumul_size; csrow->last_page = cumul_size - 1; - csrow->nr_pages = cumul_size - last_cumul_size; + nr_pages = cumul_size - last_cumul_size; last_cumul_size = cumul_size; - csrow->grain = 1 << 12; /* 4KiB - resolution of CELOG */ - csrow->mtype = MEM_RDDR; /* only one type supported */ - csrow->dtype = mem_dev ? DEV_X4 : DEV_X8; - - /* - * if single channel or x8 devices then SECDED - * if dual channel and x4 then S4ECD4ED - */ - if (drc_ddim) { - if (drc_chan && mem_dev) { - csrow->edac_mode = EDAC_S4ECD4ED; - mci->edac_cap |= EDAC_FLAG_S4ECD4ED; - } else { - csrow->edac_mode = EDAC_SECDED; - mci->edac_cap |= EDAC_FLAG_SECDED; - } - } else - csrow->edac_mode = EDAC_NONE; + + for (i = 0; i < csrow->nr_channels; i++) { + struct dimm_info *dimm = csrow->channels[i].dimm; + + debugf3("Initializing rank at (%i,%i)\n", index, i); + dimm->nr_pages = nr_pages / csrow->nr_channels; + dimm->grain = 1 << 12; /* 4KiB - resolution of CELOG */ + dimm->mtype = MEM_RDDR; /* only one type supported */ + dimm->dtype = mem_dev ? DEV_X4 : DEV_X8; + + /* + * if single channel or x8 devices then SECDED + * if dual channel and x4 then S4ECD4ED + */ + if (drc_ddim) { + if (drc_chan && mem_dev) { + dimm->edac_mode = EDAC_S4ECD4ED; + mci->edac_cap |= EDAC_FLAG_S4ECD4ED; + } else { + dimm->edac_mode = EDAC_SECDED; + mci->edac_cap |= EDAC_FLAG_SECDED; + } + } else + dimm->edac_mode = EDAC_NONE; + } } } @@ -1226,6 +1263,7 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) u16 pci_data; u8 stat8; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct e752x_pvt *pvt; u16 ddrcsr; int drc_chan; /* Number of channels 0=1chan,1=2chan */ @@ -1252,11 +1290,15 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) /* Dual channel = 1, Single channel = 0 */ drc_chan = dual_channel_active(ddrcsr); - mci = edac_mc_alloc(sizeof(*pvt), E752X_NR_CSROWS, drc_chan + 1, 0); - - if (mci == NULL) { + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = E752X_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = drc_chan + 1; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); + if (mci == NULL) return -ENOMEM; - } debugf3("%s(): init mci\n", __func__); mci->mtype_cap = MEM_FLAG_RDDR; diff --git a/drivers/edac/e7xxx_edac.c b/drivers/edac/e7xxx_edac.c index 68dea87b72e..9a9c1a54679 100644 --- a/drivers/edac/e7xxx_edac.c +++ b/drivers/edac/e7xxx_edac.c @@ -10,6 +10,9 @@ * Based on work by Dan Hollis <goemon at anime dot net> and others. * http://www.anime.net/~goemon/linux-ecc/ * + * Datasheet: + * http://www.intel.com/content/www/us/en/chipsets/e7501-chipset-memory-controller-hub-datasheet.html + * * Contributors: * Eric Biederman (Linux Networx) * Tom Zimmerman (Linux Networx) @@ -71,7 +74,7 @@ #endif /* PCI_DEVICE_ID_INTEL_7505_1_ERR */ #define E7XXX_NR_CSROWS 8 /* number of csrows */ -#define E7XXX_NR_DIMMS 8 /* FIXME - is this correct? */ +#define E7XXX_NR_DIMMS 8 /* 2 channels, 4 dimms/channel */ /* E7XXX register addresses - device 0 function 0 */ #define E7XXX_DRB 0x60 /* DRAM row boundary register (8b) */ @@ -216,13 +219,15 @@ static void process_ce(struct mem_ctl_info *mci, struct e7xxx_error_info *info) row = edac_mc_find_csrow_by_page(mci, page); /* convert syndrome to channel */ channel = e7xxx_find_channel(syndrome); - edac_mc_handle_ce(mci, page, 0, syndrome, row, channel, "e7xxx CE"); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, page, 0, syndrome, + row, channel, -1, "e7xxx CE", "", NULL); } static void process_ce_no_info(struct mem_ctl_info *mci) { debugf3("%s()\n", __func__); - edac_mc_handle_ce_no_info(mci, "e7xxx CE log register overflow"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, -1, -1, -1, + "e7xxx CE log register overflow", "", NULL); } static void process_ue(struct mem_ctl_info *mci, struct e7xxx_error_info *info) @@ -236,13 +241,17 @@ static void process_ue(struct mem_ctl_info *mci, struct e7xxx_error_info *info) /* FIXME - should use PAGE_SHIFT */ block_page = error_2b >> 6; /* convert to 4k address */ row = edac_mc_find_csrow_by_page(mci, block_page); - edac_mc_handle_ue(mci, block_page, 0, row, "e7xxx UE"); + + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, block_page, 0, 0, + row, -1, -1, "e7xxx UE", "", NULL); } static void process_ue_no_info(struct mem_ctl_info *mci) { debugf3("%s()\n", __func__); - edac_mc_handle_ue_no_info(mci, "e7xxx UE log register overflow"); + + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, -1, -1, -1, + "e7xxx UE log register overflow", "", NULL); } static void e7xxx_get_error_info(struct mem_ctl_info *mci, @@ -347,11 +356,12 @@ static void e7xxx_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, int dev_idx, u32 drc) { unsigned long last_cumul_size; - int index; + int index, j; u8 value; - u32 dra, cumul_size; + u32 dra, cumul_size, nr_pages; int drc_chan, drc_drbg, drc_ddim, mem_dev; struct csrow_info *csrow; + struct dimm_info *dimm; pci_read_config_dword(pdev, E7XXX_DRA, &dra); drc_chan = dual_channel_active(drc, dev_idx); @@ -379,26 +389,32 @@ static void e7xxx_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, csrow->first_page = last_cumul_size; csrow->last_page = cumul_size - 1; - csrow->nr_pages = cumul_size - last_cumul_size; + nr_pages = cumul_size - last_cumul_size; last_cumul_size = cumul_size; - csrow->grain = 1 << 12; /* 4KiB - resolution of CELOG */ - csrow->mtype = MEM_RDDR; /* only one type supported */ - csrow->dtype = mem_dev ? DEV_X4 : DEV_X8; - - /* - * if single channel or x8 devices then SECDED - * if dual channel and x4 then S4ECD4ED - */ - if (drc_ddim) { - if (drc_chan && mem_dev) { - csrow->edac_mode = EDAC_S4ECD4ED; - mci->edac_cap |= EDAC_FLAG_S4ECD4ED; - } else { - csrow->edac_mode = EDAC_SECDED; - mci->edac_cap |= EDAC_FLAG_SECDED; - } - } else - csrow->edac_mode = EDAC_NONE; + + for (j = 0; j < drc_chan + 1; j++) { + dimm = csrow->channels[j].dimm; + + dimm->nr_pages = nr_pages / (drc_chan + 1); + dimm->grain = 1 << 12; /* 4KiB - resolution of CELOG */ + dimm->mtype = MEM_RDDR; /* only one type supported */ + dimm->dtype = mem_dev ? DEV_X4 : DEV_X8; + + /* + * if single channel or x8 devices then SECDED + * if dual channel and x4 then S4ECD4ED + */ + if (drc_ddim) { + if (drc_chan && mem_dev) { + dimm->edac_mode = EDAC_S4ECD4ED; + mci->edac_cap |= EDAC_FLAG_S4ECD4ED; + } else { + dimm->edac_mode = EDAC_SECDED; + mci->edac_cap |= EDAC_FLAG_SECDED; + } + } else + dimm->edac_mode = EDAC_NONE; + } } } @@ -406,6 +422,7 @@ static int e7xxx_probe1(struct pci_dev *pdev, int dev_idx) { u16 pci_data; struct mem_ctl_info *mci = NULL; + struct edac_mc_layer layers[2]; struct e7xxx_pvt *pvt = NULL; u32 drc; int drc_chan; @@ -416,8 +433,21 @@ static int e7xxx_probe1(struct pci_dev *pdev, int dev_idx) pci_read_config_dword(pdev, E7XXX_DRC, &drc); drc_chan = dual_channel_active(drc, dev_idx); - mci = edac_mc_alloc(sizeof(*pvt), E7XXX_NR_CSROWS, drc_chan + 1, 0); - + /* + * According with the datasheet, this device has a maximum of + * 4 DIMMS per channel, either single-rank or dual-rank. So, the + * total amount of dimms is 8 (E7XXX_NR_DIMMS). + * That means that the DIMM is mapped as CSROWs, and the channel + * will map the rank. So, an error to either channel should be + * attributed to the same dimm. + */ + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = E7XXX_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = drc_chan + 1; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (mci == NULL) return -ENOMEM; diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h index 5b739411d62..117490d4f83 100644 --- a/drivers/edac/edac_core.h +++ b/drivers/edac/edac_core.h @@ -447,8 +447,10 @@ static inline void pci_write_bits32(struct pci_dev *pdev, int offset, #endif /* CONFIG_PCI */ -extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, - unsigned nr_chans, int edac_index); +struct mem_ctl_info *edac_mc_alloc(unsigned mc_num, + unsigned n_layers, + struct edac_mc_layer *layers, + unsigned sz_pvt); extern int edac_mc_add_mc(struct mem_ctl_info *mci); extern void edac_mc_free(struct mem_ctl_info *mci); extern struct mem_ctl_info *edac_mc_find(int idx); @@ -456,35 +458,17 @@ extern struct mem_ctl_info *find_mci_by_dev(struct device *dev); extern struct mem_ctl_info *edac_mc_del_mc(struct device *dev); extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page); - -/* - * The no info errors are used when error overflows are reported. - * There are a limited number of error logging registers that can - * be exausted. When all registers are exhausted and an additional - * error occurs then an error overflow register records that an - * error occurred and the type of error, but doesn't have any - * further information. The ce/ue versions make for cleaner - * reporting logic and function interface - reduces conditional - * statement clutter and extra function arguments. - */ -extern void edac_mc_handle_ce(struct mem_ctl_info *mci, - unsigned long page_frame_number, - unsigned long offset_in_page, - unsigned long syndrome, int row, int channel, - const char *msg); -extern void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, - const char *msg); -extern void edac_mc_handle_ue(struct mem_ctl_info *mci, - unsigned long page_frame_number, - unsigned long offset_in_page, int row, - const char *msg); -extern void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, - const char *msg); -extern void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, unsigned int csrow, - unsigned int channel0, unsigned int channel1, - char *msg); -extern void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, unsigned int csrow, - unsigned int channel, char *msg); +void edac_mc_handle_error(const enum hw_event_mc_err_type type, + struct mem_ctl_info *mci, + const unsigned long page_frame_number, + const unsigned long offset_in_page, + const unsigned long syndrome, + const int layer0, + const int layer1, + const int layer2, + const char *msg, + const char *other_detail, + const void *mcelog); /* * edac_device APIs @@ -496,6 +480,7 @@ extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev, extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev, int inst_nr, int block_nr, const char *msg); extern int edac_device_alloc_index(void); +extern const char *edac_layer_name[]; /* * edac_pci APIs diff --git a/drivers/edac/edac_device.c b/drivers/edac/edac_device.c index 45b8f4bdd77..ee3f1f810c1 100644 --- a/drivers/edac/edac_device.c +++ b/drivers/edac/edac_device.c @@ -79,7 +79,7 @@ struct edac_device_ctl_info *edac_device_alloc_ctl_info( unsigned total_size; unsigned count; unsigned instance, block, attr; - void *pvt; + void *pvt, *p; int err; debugf4("%s() instances=%d blocks=%d\n", @@ -92,35 +92,30 @@ struct edac_device_ctl_info *edac_device_alloc_ctl_info( * to be at least as stringent as what the compiler would * provide if we could simply hardcode everything into a single struct. */ - dev_ctl = (struct edac_device_ctl_info *)NULL; + p = NULL; + dev_ctl = edac_align_ptr(&p, sizeof(*dev_ctl), 1); /* Calc the 'end' offset past end of ONE ctl_info structure * which will become the start of the 'instance' array */ - dev_inst = edac_align_ptr(&dev_ctl[1], sizeof(*dev_inst)); + dev_inst = edac_align_ptr(&p, sizeof(*dev_inst), nr_instances); /* Calc the 'end' offset past the instance array within the ctl_info * which will become the start of the block array */ - dev_blk = edac_align_ptr(&dev_inst[nr_instances], sizeof(*dev_blk)); + count = nr_instances * nr_blocks; + dev_blk = edac_align_ptr(&p, sizeof(*dev_blk), count); /* Calc the 'end' offset past the dev_blk array * which will become the start of the attrib array, if any. */ - count = nr_instances * nr_blocks; - dev_attrib = edac_align_ptr(&dev_blk[count], sizeof(*dev_attrib)); - - /* Check for case of when an attribute array is specified */ - if (nr_attrib > 0) { - /* calc how many nr_attrib we need */ + /* calc how many nr_attrib we need */ + if (nr_attrib > 0) count *= nr_attrib; + dev_attrib = edac_align_ptr(&p, sizeof(*dev_attrib), count); - /* Calc the 'end' offset past the attributes array */ - pvt = edac_align_ptr(&dev_attrib[count], sz_private); - } else { - /* no attribute array specified */ - pvt = edac_align_ptr(dev_attrib, sz_private); - } + /* Calc the 'end' offset past the attributes array */ + pvt = edac_align_ptr(&p, sz_private, 1); /* 'pvt' now points to where the private data area is. * At this point 'pvt' (like dev_inst,dev_blk and dev_attrib) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index feef7733fae..10f375032e9 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -43,9 +43,26 @@ static void edac_mc_dump_channel(struct rank_info *chan) { debugf4("\tchannel = %p\n", chan); debugf4("\tchannel->chan_idx = %d\n", chan->chan_idx); - debugf4("\tchannel->ce_count = %d\n", chan->ce_count); - debugf4("\tchannel->label = '%s'\n", chan->label); debugf4("\tchannel->csrow = %p\n\n", chan->csrow); + debugf4("\tchannel->dimm = %p\n", chan->dimm); +} + +static void edac_mc_dump_dimm(struct dimm_info *dimm) +{ + int i; + + debugf4("\tdimm = %p\n", dimm); + debugf4("\tdimm->label = '%s'\n", dimm->label); + debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages); + debugf4("\tdimm location "); + for (i = 0; i < dimm->mci->n_layers; i++) { + printk(KERN_CONT "%d", dimm->location[i]); + if (i < dimm->mci->n_layers - 1) + printk(KERN_CONT "."); + } + printk(KERN_CONT "\n"); + debugf4("\tdimm->grain = %d\n", dimm->grain); + debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages); } static void edac_mc_dump_csrow(struct csrow_info *csrow) @@ -55,7 +72,6 @@ static void edac_mc_dump_csrow(struct csrow_info *csrow) debugf4("\tcsrow->first_page = 0x%lx\n", csrow->first_page); debugf4("\tcsrow->last_page = 0x%lx\n", csrow->last_page); debugf4("\tcsrow->page_mask = 0x%lx\n", csrow->page_mask); - debugf4("\tcsrow->nr_pages = 0x%x\n", csrow->nr_pages); debugf4("\tcsrow->nr_channels = %d\n", csrow->nr_channels); debugf4("\tcsrow->channels = %p\n", csrow->channels); debugf4("\tcsrow->mci = %p\n\n", csrow->mci); @@ -70,6 +86,8 @@ static void edac_mc_dump_mci(struct mem_ctl_info *mci) debugf4("\tmci->edac_check = %p\n", mci->edac_check); debugf3("\tmci->nr_csrows = %d, csrows = %p\n", mci->nr_csrows, mci->csrows); + debugf3("\tmci->nr_dimms = %d, dimms = %p\n", + mci->tot_dimms, mci->dimms); debugf3("\tdev = %p\n", mci->dev); debugf3("\tmod_name:ctl_name = %s:%s\n", mci->mod_name, mci->ctl_name); debugf3("\tpvt_info = %p\n\n", mci->pvt_info); @@ -101,18 +119,37 @@ const char *edac_mem_types[] = { }; EXPORT_SYMBOL_GPL(edac_mem_types); -/* 'ptr' points to a possibly unaligned item X such that sizeof(X) is 'size'. - * Adjust 'ptr' so that its alignment is at least as stringent as what the - * compiler would provide for X and return the aligned result. +/** + * edac_align_ptr - Prepares the pointer offsets for a single-shot allocation + * @p: pointer to a pointer with the memory offset to be used. At + * return, this will be incremented to point to the next offset + * @size: Size of the data structure to be reserved + * @n_elems: Number of elements that should be reserved * * If 'size' is a constant, the compiler will optimize this whole function - * down to either a no-op or the addition of a constant to the value of 'ptr'. + * down to either a no-op or the addition of a constant to the value of '*p'. + * + * The 'p' pointer is absolutely needed to keep the proper advancing + * further in memory to the proper offsets when allocating the struct along + * with its embedded structs, as edac_device_alloc_ctl_info() does it + * above, for example. + * + * At return, the pointer 'p' will be incremented to be used on a next call + * to this function. */ -void *edac_align_ptr(void *ptr, unsigned size) +void *edac_align_ptr(void **p, unsigned size, int n_elems) { unsigned align, r; + void *ptr = *p; + + *p += size * n_elems; - /* Here we assume that the alignment of a "long long" is the most + /* + * 'p' can possibly be an unaligned item X such that sizeof(X) is + * 'size'. Adjust 'p' so that its alignment is at least as + * stringent as what the compiler would provide for X and return + * the aligned result. + * Here we assume that the alignment of a "long long" is the most * stringent alignment that the compiler will ever provide by default. * As far as I know, this is a reasonable assumption. */ @@ -132,14 +169,18 @@ void *edac_align_ptr(void *ptr, unsigned size) if (r == 0) return (char *)ptr; + *p += align - r; + return (void *)(((unsigned long)ptr) + align - r); } /** - * edac_mc_alloc: Allocate a struct mem_ctl_info structure - * @size_pvt: size of private storage needed - * @nr_csrows: Number of CWROWS needed for this MC - * @nr_chans: Number of channels for the MC + * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure + * @mc_num: Memory controller number + * @n_layers: Number of MC hierarchy layers + * layers: Describes each layer as seen by the Memory Controller + * @size_pvt: size of private storage needed + * * * Everything is kmalloc'ed as one big chunk - more efficient. * Only can be used if all structures have the same lifetime - otherwise @@ -147,32 +188,77 @@ void *edac_align_ptr(void *ptr, unsigned size) * * Use edac_mc_free() to free mc structures allocated by this function. * + * NOTE: drivers handle multi-rank memories in different ways: in some + * drivers, one multi-rank memory stick is mapped as one entry, while, in + * others, a single multi-rank memory stick would be mapped into several + * entries. Currently, this function will allocate multiple struct dimm_info + * on such scenarios, as grouping the multiple ranks require drivers change. + * * Returns: - * NULL allocation failed - * struct mem_ctl_info pointer + * On failure: NULL + * On success: struct mem_ctl_info pointer */ -struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, - unsigned nr_chans, int edac_index) +struct mem_ctl_info *edac_mc_alloc(unsigned mc_num, + unsigned n_layers, + struct edac_mc_layer *layers, + unsigned sz_pvt) { struct mem_ctl_info *mci; - struct csrow_info *csi, *csrow; + struct edac_mc_layer *layer; + struct csrow_info *csi, *csr; struct rank_info *chi, *chp, *chan; - void *pvt; - unsigned size; - int row, chn; - int err; + struct dimm_info *dimm; + u32 *ce_per_layer[EDAC_MAX_LAYERS], *ue_per_layer[EDAC_MAX_LAYERS]; + unsigned pos[EDAC_MAX_LAYERS]; + unsigned size, tot_dimms = 1, count = 1; + unsigned tot_csrows = 1, tot_channels = 1, tot_errcount = 0; + void *pvt, *p, *ptr = NULL; + int i, j, err, row, chn, n, len; + bool per_rank = false; + + BUG_ON(n_layers > EDAC_MAX_LAYERS || n_layers == 0); + /* + * Calculate the total amount of dimms and csrows/cschannels while + * in the old API emulation mode + */ + for (i = 0; i < n_layers; i++) { + tot_dimms *= layers[i].size; + if (layers[i].is_virt_csrow) + tot_csrows *= layers[i].size; + else + tot_channels *= layers[i].size; + + if (layers[i].type == EDAC_MC_LAYER_CHIP_SELECT) + per_rank = true; + } /* Figure out the offsets of the various items from the start of an mc * structure. We want the alignment of each item to be at least as * stringent as what the compiler would provide if we could simply * hardcode everything into a single struct. */ - mci = (struct mem_ctl_info *)0; - csi = edac_align_ptr(&mci[1], sizeof(*csi)); - chi = edac_align_ptr(&csi[nr_csrows], sizeof(*chi)); - pvt = edac_align_ptr(&chi[nr_chans * nr_csrows], sz_pvt); + mci = edac_align_ptr(&ptr, sizeof(*mci), 1); + layer = edac_align_ptr(&ptr, sizeof(*layer), n_layers); + csi = edac_align_ptr(&ptr, sizeof(*csi), tot_csrows); + chi = edac_align_ptr(&ptr, sizeof(*chi), tot_csrows * tot_channels); + dimm = edac_align_ptr(&ptr, sizeof(*dimm), tot_dimms); + for (i = 0; i < n_layers; i++) { + count *= layers[i].size; + debugf4("%s: errcount layer %d size %d\n", __func__, i, count); + ce_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count); + ue_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count); + tot_errcount += 2 * count; + } + + debugf4("%s: allocating %d error counters\n", __func__, tot_errcount); + pvt = edac_align_ptr(&ptr, sz_pvt, 1); size = ((unsigned long)pvt) + sz_pvt; + debugf1("%s(): allocating %u bytes for mci data (%d %s, %d csrows/channels)\n", + __func__, size, + tot_dimms, + per_rank ? "ranks" : "dimms", + tot_csrows * tot_channels); mci = kzalloc(size, GFP_KERNEL); if (mci == NULL) return NULL; @@ -180,28 +266,103 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows, /* Adjust pointers so they point within the memory we just allocated * rather than an imaginary chunk of memory located at address 0. */ + layer = (struct edac_mc_layer *)(((char *)mci) + ((unsigned long)layer)); csi = (struct csrow_info *)(((char *)mci) + ((unsigned long)csi)); chi = (struct rank_info *)(((char *)mci) + ((unsigned long)chi)); + dimm = (struct dimm_info *)(((char *)mci) + ((unsigned long)dimm)); + for (i = 0; i < n_layers; i++) { + mci->ce_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ce_per_layer[i])); + mci->ue_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ue_per_layer[i])); + } pvt = sz_pvt ? (((char *)mci) + ((unsigned long)pvt)) : NULL; /* setup index and various internal pointers */ - mci->mc_idx = edac_index; + mci->mc_idx = mc_num; mci->csrows = csi; + mci->dimms = dimm; + mci->tot_dimms = tot_dimms; mci->pvt_info = pvt; - mci->nr_csrows = nr_csrows; - - for (row = 0; row < nr_csrows; row++) { - csrow = &csi[row]; - csrow->csrow_idx = row; - csrow->mci = mci; - csrow->nr_channels = nr_chans; - chp = &chi[row * nr_chans]; - csrow->channels = chp; + mci->n_layers = n_layers; + mci->layers = layer; + memcpy(mci->layers, layers, sizeof(*layer) * n_layers); + mci->nr_csrows = tot_csrows; + mci->num_cschannel = tot_channels; + mci->mem_is_per_rank = per_rank; - for (chn = 0; chn < nr_chans; chn++) { + /* + * Fill the csrow struct + */ + for (row = 0; row < tot_csrows; row++) { + csr = &csi[row]; + csr->csrow_idx = row; + csr->mci = mci; + csr->nr_channels = tot_channels; + chp = &chi[row * tot_channels]; + csr->channels = chp; + + for (chn = 0; chn < tot_channels; chn++) { chan = &chp[chn]; chan->chan_idx = chn; - chan->csrow = csrow; + chan->csrow = csr; + } + } + + /* + * Fill the dimm struct + */ + memset(&pos, 0, sizeof(pos)); + row = 0; + chn = 0; + debugf4("%s: initializing %d %s\n", __func__, tot_dimms, + per_rank ? "ranks" : "dimms"); + for (i = 0; i < tot_dimms; i++) { + chan = &csi[row].channels[chn]; + dimm = EDAC_DIMM_PTR(layer, mci->dimms, n_layers, + pos[0], pos[1], pos[2]); + dimm->mci = mci; + + debugf2("%s: %d: %s%zd (%d:%d:%d): row %d, chan %d\n", __func__, + i, per_rank ? "rank" : "dimm", (dimm - mci->dimms), + pos[0], pos[1], pos[2], row, chn); + + /* + * Copy DIMM location and initialize it. + */ + len = sizeof(dimm->label); + p = dimm->label; + n = snprintf(p, len, "mc#%u", mc_num); + p += n; + len -= n; + for (j = 0; j < n_layers; j++) { + n = snprintf(p, len, "%s#%u", + edac_layer_name[layers[j].type], + pos[j]); + p += n; + len -= n; + dimm->location[j] = pos[j]; + + if (len <= 0) + break; + } + + /* Link it to the csrows old API data */ + chan->dimm = dimm; + dimm->csrow = row; + dimm->cschannel = chn; + + /* Increment csrow location */ + row++; + if (row == tot_csrows) { + row = 0; + chn++; + } + + /* Increment dimm location */ + for (j = n_layers - 1; j >= 0; j--) { + pos[j]++; + if (pos[j] < layers[j].size) + break; + pos[j] = 0; } } @@ -490,7 +651,6 @@ EXPORT_SYMBOL(edac_mc_find); * edac_mc_add_mc: Insert the 'mci' structure into the mci global list and * create sysfs entries associated with mci structure * @mci: pointer to the mci structure to be added to the list - * @mc_idx: A unique numeric identifier to be assigned to the 'mci' structure. * * Return: * 0 Success @@ -517,6 +677,8 @@ int edac_mc_add_mc(struct mem_ctl_info *mci) edac_mc_dump_channel(&mci->csrows[i]. channels[j]); } + for (i = 0; i < mci->tot_dimms; i++) + edac_mc_dump_dimm(&mci->dimms[i]); } #endif mutex_lock(&mem_ctls_mutex); @@ -636,15 +798,19 @@ static void edac_mc_scrub_block(unsigned long page, unsigned long offset, int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page) { struct csrow_info *csrows = mci->csrows; - int row, i; + int row, i, j, n; debugf1("MC%d: %s(): 0x%lx\n", mci->mc_idx, __func__, page); row = -1; for (i = 0; i < mci->nr_csrows; i++) { struct csrow_info *csrow = &csrows[i]; - - if (csrow->nr_pages == 0) + n = 0; + for (j = 0; j < csrow->nr_channels; j++) { + struct dimm_info *dimm = csrow->channels[j].dimm; + n += dimm->nr_pages; + } + if (n == 0) continue; debugf3("MC%d: %s(): first(0x%lx) page(0x%lx) last(0x%lx) " @@ -670,249 +836,307 @@ int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page) } EXPORT_SYMBOL_GPL(edac_mc_find_csrow_by_page); -/* FIXME - setable log (warning/emerg) levels */ -/* FIXME - integrate with evlog: http://evlog.sourceforge.net/ */ -void edac_mc_handle_ce(struct mem_ctl_info *mci, - unsigned long page_frame_number, - unsigned long offset_in_page, unsigned long syndrome, - int row, int channel, const char *msg) -{ - unsigned long remapped_page; +const char *edac_layer_name[] = { + [EDAC_MC_LAYER_BRANCH] = "branch", + [EDAC_MC_LAYER_CHANNEL] = "channel", + [EDAC_MC_LAYER_SLOT] = "slot", + [EDAC_MC_LAYER_CHIP_SELECT] = "csrow", +}; +EXPORT_SYMBOL_GPL(edac_layer_name); - debugf3("MC%d: %s()\n", mci->mc_idx, __func__); +static void edac_inc_ce_error(struct mem_ctl_info *mci, + bool enable_per_layer_report, + const int pos[EDAC_MAX_LAYERS]) +{ + int i, index = 0; - /* FIXME - maybe make panic on INTERNAL ERROR an option */ - if (row >= mci->nr_csrows || row < 0) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: row out of range " - "(%d >= %d)\n", row, mci->nr_csrows); - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR"); - return; - } + mci->ce_mc++; - if (channel >= mci->csrows[row].nr_channels || channel < 0) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: channel out of range " - "(%d >= %d)\n", channel, - mci->csrows[row].nr_channels); - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR"); + if (!enable_per_layer_report) { + mci->ce_noinfo_count++; return; } - if (edac_mc_get_log_ce()) - /* FIXME - put in DIMM location */ - edac_mc_printk(mci, KERN_WARNING, - "CE page 0x%lx, offset 0x%lx, grain %d, syndrome " - "0x%lx, row %d, channel %d, label \"%s\": %s\n", - page_frame_number, offset_in_page, - mci->csrows[row].grain, syndrome, row, channel, - mci->csrows[row].channels[channel].label, msg); - - mci->ce_count++; - mci->csrows[row].ce_count++; - mci->csrows[row].channels[channel].ce_count++; - - if (mci->scrub_mode & SCRUB_SW_SRC) { - /* - * Some MC's can remap memory so that it is still available - * at a different address when PCI devices map into memory. - * MC's that can't do this lose the memory where PCI devices - * are mapped. This mapping is MC dependent and so we call - * back into the MC driver for it to map the MC page to - * a physical (CPU) page which can then be mapped to a virtual - * page - which can then be scrubbed. - */ - remapped_page = mci->ctl_page_to_phys ? - mci->ctl_page_to_phys(mci, page_frame_number) : - page_frame_number; + for (i = 0; i < mci->n_layers; i++) { + if (pos[i] < 0) + break; + index += pos[i]; + mci->ce_per_layer[i][index]++; - edac_mc_scrub_block(remapped_page, offset_in_page, - mci->csrows[row].grain); + if (i < mci->n_layers - 1) + index *= mci->layers[i + 1].size; } } -EXPORT_SYMBOL_GPL(edac_mc_handle_ce); -void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, const char *msg) +static void edac_inc_ue_error(struct mem_ctl_info *mci, + bool enable_per_layer_report, + const int pos[EDAC_MAX_LAYERS]) { - if (edac_mc_get_log_ce()) - edac_mc_printk(mci, KERN_WARNING, - "CE - no information available: %s\n", msg); + int i, index = 0; - mci->ce_noinfo_count++; - mci->ce_count++; -} -EXPORT_SYMBOL_GPL(edac_mc_handle_ce_no_info); + mci->ue_mc++; -void edac_mc_handle_ue(struct mem_ctl_info *mci, - unsigned long page_frame_number, - unsigned long offset_in_page, int row, const char *msg) -{ - int len = EDAC_MC_LABEL_LEN * 4; - char labels[len + 1]; - char *pos = labels; - int chan; - int chars; - - debugf3("MC%d: %s()\n", mci->mc_idx, __func__); - - /* FIXME - maybe make panic on INTERNAL ERROR an option */ - if (row >= mci->nr_csrows || row < 0) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: row out of range " - "(%d >= %d)\n", row, mci->nr_csrows); - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR"); + if (!enable_per_layer_report) { + mci->ce_noinfo_count++; return; } - chars = snprintf(pos, len + 1, "%s", - mci->csrows[row].channels[0].label); - len -= chars; - pos += chars; + for (i = 0; i < mci->n_layers; i++) { + if (pos[i] < 0) + break; + index += pos[i]; + mci->ue_per_layer[i][index]++; - for (chan = 1; (chan < mci->csrows[row].nr_channels) && (len > 0); - chan++) { - chars = snprintf(pos, len + 1, ":%s", - mci->csrows[row].channels[chan].label); - len -= chars; - pos += chars; + if (i < mci->n_layers - 1) + index *= mci->layers[i + 1].size; } +} - if (edac_mc_get_log_ue()) - edac_mc_printk(mci, KERN_EMERG, - "UE page 0x%lx, offset 0x%lx, grain %d, row %d, " - "labels \"%s\": %s\n", page_frame_number, - offset_in_page, mci->csrows[row].grain, row, - labels, msg); +static void edac_ce_error(struct mem_ctl_info *mci, + const int pos[EDAC_MAX_LAYERS], + const char *msg, + const char *location, + const char *label, + const char *detail, + const char *other_detail, + const bool enable_per_layer_report, + const unsigned long page_frame_number, + const unsigned long offset_in_page, + u32 grain) +{ + unsigned long remapped_page; - if (edac_mc_get_panic_on_ue()) - panic("EDAC MC%d: UE page 0x%lx, offset 0x%lx, grain %d, " - "row %d, labels \"%s\": %s\n", mci->mc_idx, - page_frame_number, offset_in_page, - mci->csrows[row].grain, row, labels, msg); + if (edac_mc_get_log_ce()) { + if (other_detail && *other_detail) + edac_mc_printk(mci, KERN_WARNING, + "CE %s on %s (%s%s - %s)\n", + msg, label, location, + detail, other_detail); + else + edac_mc_printk(mci, KERN_WARNING, + "CE %s on %s (%s%s)\n", + msg, label, location, + detail); + } + edac_inc_ce_error(mci, enable_per_layer_report, pos); - mci->ue_count++; - mci->csrows[row].ue_count++; + if (mci->scrub_mode & SCRUB_SW_SRC) { + /* + * Some memory controllers (called MCs below) can remap + * memory so that it is still available at a different + * address when PCI devices map into memory. + * MC's that can't do this, lose the memory where PCI + * devices are mapped. This mapping is MC-dependent + * and so we call back into the MC driver for it to + * map the MC page to a physical (CPU) page which can + * then be mapped to a virtual page - which can then + * be scrubbed. + */ + remapped_page = mci->ctl_page_to_phys ? + mci->ctl_page_to_phys(mci, page_frame_number) : + page_frame_number; + + edac_mc_scrub_block(remapped_page, + offset_in_page, grain); + } } -EXPORT_SYMBOL_GPL(edac_mc_handle_ue); -void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, const char *msg) +static void edac_ue_error(struct mem_ctl_info *mci, + const int pos[EDAC_MAX_LAYERS], + const char *msg, + const char *location, + const char *label, + const char *detail, + const char *other_detail, + const bool enable_per_layer_report) { - if (edac_mc_get_panic_on_ue()) - panic("EDAC MC%d: Uncorrected Error", mci->mc_idx); + if (edac_mc_get_log_ue()) { + if (other_detail && *other_detail) + edac_mc_printk(mci, KERN_WARNING, + "UE %s on %s (%s%s - %s)\n", + msg, label, location, detail, + other_detail); + else + edac_mc_printk(mci, KERN_WARNING, + "UE %s on %s (%s%s)\n", + msg, label, location, detail); + } - if (edac_mc_get_log_ue()) - edac_mc_printk(mci, KERN_WARNING, - "UE - no information available: %s\n", msg); - mci->ue_noinfo_count++; - mci->ue_count++; + if (edac_mc_get_panic_on_ue()) { + if (other_detail && *other_detail) + panic("UE %s on %s (%s%s - %s)\n", + msg, label, location, detail, other_detail); + else + panic("UE %s on %s (%s%s)\n", + msg, label, location, detail); + } + + edac_inc_ue_error(mci, enable_per_layer_report, pos); } -EXPORT_SYMBOL_GPL(edac_mc_handle_ue_no_info); -/************************************************************* - * On Fully Buffered DIMM modules, this help function is - * called to process UE events - */ -void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, - unsigned int csrow, - unsigned int channela, - unsigned int channelb, char *msg) +#define OTHER_LABEL " or " +void edac_mc_handle_error(const enum hw_event_mc_err_type type, + struct mem_ctl_info *mci, + const unsigned long page_frame_number, + const unsigned long offset_in_page, + const unsigned long syndrome, + const int layer0, + const int layer1, + const int layer2, + const char *msg, + const char *other_detail, + const void *mcelog) { - int len = EDAC_MC_LABEL_LEN * 4; - char labels[len + 1]; - char *pos = labels; - int chars; + /* FIXME: too much for stack: move it to some pre-alocated area */ + char detail[80], location[80]; + char label[(EDAC_MC_LABEL_LEN + 1 + sizeof(OTHER_LABEL)) * mci->tot_dimms]; + char *p; + int row = -1, chan = -1; + int pos[EDAC_MAX_LAYERS] = { layer0, layer1, layer2 }; + int i; + u32 grain; + bool enable_per_layer_report = false; - if (csrow >= mci->nr_csrows) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: row out of range (%d >= %d)\n", - csrow, mci->nr_csrows); - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR"); - return; - } + debugf3("MC%d: %s()\n", mci->mc_idx, __func__); - if (channela >= mci->csrows[csrow].nr_channels) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: channel-a out of range " - "(%d >= %d)\n", - channela, mci->csrows[csrow].nr_channels); - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR"); - return; + /* + * Check if the event report is consistent and if the memory + * location is known. If it is known, enable_per_layer_report will be + * true, the DIMM(s) label info will be filled and the per-layer + * error counters will be incremented. + */ + for (i = 0; i < mci->n_layers; i++) { + if (pos[i] >= (int)mci->layers[i].size) { + if (type == HW_EVENT_ERR_CORRECTED) + p = "CE"; + else + p = "UE"; + + edac_mc_printk(mci, KERN_ERR, + "INTERNAL ERROR: %s value is out of range (%d >= %d)\n", + edac_layer_name[mci->layers[i].type], + pos[i], mci->layers[i].size); + /* + * Instead of just returning it, let's use what's + * known about the error. The increment routines and + * the DIMM filter logic will do the right thing by + * pointing the likely damaged DIMMs. + */ + pos[i] = -1; + } + if (pos[i] >= 0) + enable_per_layer_report = true; } - if (channelb >= mci->csrows[csrow].nr_channels) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: channel-b out of range " - "(%d >= %d)\n", - channelb, mci->csrows[csrow].nr_channels); - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR"); - return; - } + /* + * Get the dimm label/grain that applies to the match criteria. + * As the error algorithm may not be able to point to just one memory + * stick, the logic here will get all possible labels that could + * pottentially be affected by the error. + * On FB-DIMM memory controllers, for uncorrected errors, it is common + * to have only the MC channel and the MC dimm (also called "branch") + * but the channel is not known, as the memory is arranged in pairs, + * where each memory belongs to a separate channel within the same + * branch. + */ + grain = 0; + p = label; + *p = '\0'; + for (i = 0; i < mci->tot_dimms; i++) { + struct dimm_info *dimm = &mci->dimms[i]; - mci->ue_count++; - mci->csrows[csrow].ue_count++; + if (layer0 >= 0 && layer0 != dimm->location[0]) + continue; + if (layer1 >= 0 && layer1 != dimm->location[1]) + continue; + if (layer2 >= 0 && layer2 != dimm->location[2]) + continue; - /* Generate the DIMM labels from the specified channels */ - chars = snprintf(pos, len + 1, "%s", - mci->csrows[csrow].channels[channela].label); - len -= chars; - pos += chars; - chars = snprintf(pos, len + 1, "-%s", - mci->csrows[csrow].channels[channelb].label); + /* get the max grain, over the error match range */ + if (dimm->grain > grain) + grain = dimm->grain; - if (edac_mc_get_log_ue()) - edac_mc_printk(mci, KERN_EMERG, - "UE row %d, channel-a= %d channel-b= %d " - "labels \"%s\": %s\n", csrow, channela, channelb, - labels, msg); + /* + * If the error is memory-controller wide, there's no need to + * seek for the affected DIMMs because the whole + * channel/memory controller/... may be affected. + * Also, don't show errors for empty DIMM slots. + */ + if (enable_per_layer_report && dimm->nr_pages) { + if (p != label) { + strcpy(p, OTHER_LABEL); + p += strlen(OTHER_LABEL); + } + strcpy(p, dimm->label); + p += strlen(p); + *p = '\0'; + + /* + * get csrow/channel of the DIMM, in order to allow + * incrementing the compat API counters + */ + debugf4("%s: %s csrows map: (%d,%d)\n", + __func__, + mci->mem_is_per_rank ? "rank" : "dimm", + dimm->csrow, dimm->cschannel); + + if (row == -1) + row = dimm->csrow; + else if (row >= 0 && row != dimm->csrow) + row = -2; + + if (chan == -1) + chan = dimm->cschannel; + else if (chan >= 0 && chan != dimm->cschannel) + chan = -2; + } + } - if (edac_mc_get_panic_on_ue()) - panic("UE row %d, channel-a= %d channel-b= %d " - "labels \"%s\": %s\n", csrow, channela, - channelb, labels, msg); -} -EXPORT_SYMBOL(edac_mc_handle_fbd_ue); + if (!enable_per_layer_report) { + strcpy(label, "any memory"); + } else { + debugf4("%s: csrow/channel to increment: (%d,%d)\n", + __func__, row, chan); + if (p == label) + strcpy(label, "unknown memory"); + if (type == HW_EVENT_ERR_CORRECTED) { + if (row >= 0) { + mci->csrows[row].ce_count++; + if (chan >= 0) + mci->csrows[row].channels[chan].ce_count++; + } + } else + if (row >= 0) + mci->csrows[row].ue_count++; + } -/************************************************************* - * On Fully Buffered DIMM modules, this help function is - * called to process CE events - */ -void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, - unsigned int csrow, unsigned int channel, char *msg) -{ + /* Fill the RAM location data */ + p = location; + for (i = 0; i < mci->n_layers; i++) { + if (pos[i] < 0) + continue; - /* Ensure boundary values */ - if (csrow >= mci->nr_csrows) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: row out of range (%d >= %d)\n", - csrow, mci->nr_csrows); - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR"); - return; - } - if (channel >= mci->csrows[csrow].nr_channels) { - /* something is wrong */ - edac_mc_printk(mci, KERN_ERR, - "INTERNAL ERROR: channel out of range (%d >= %d)\n", - channel, mci->csrows[csrow].nr_channels); - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR"); - return; + p += sprintf(p, "%s:%d ", + edac_layer_name[mci->layers[i].type], + pos[i]); } - if (edac_mc_get_log_ce()) - /* FIXME - put in DIMM location */ - edac_mc_printk(mci, KERN_WARNING, - "CE row %d, channel %d, label \"%s\": %s\n", - csrow, channel, - mci->csrows[csrow].channels[channel].label, msg); + /* Memory type dependent details about the error */ + if (type == HW_EVENT_ERR_CORRECTED) { + snprintf(detail, sizeof(detail), + "page:0x%lx offset:0x%lx grain:%d syndrome:0x%lx", + page_frame_number, offset_in_page, + grain, syndrome); + edac_ce_error(mci, pos, msg, location, label, detail, + other_detail, enable_per_layer_report, + page_frame_number, offset_in_page, grain); + } else { + snprintf(detail, sizeof(detail), + "page:0x%lx offset:0x%lx grain:%d", + page_frame_number, offset_in_page, grain); - mci->ce_count++; - mci->csrows[csrow].ce_count++; - mci->csrows[csrow].channels[channel].ce_count++; + edac_ue_error(mci, pos, msg, location, label, detail, + other_detail, enable_per_layer_report); + } } -EXPORT_SYMBOL(edac_mc_handle_fbd_ce); +EXPORT_SYMBOL_GPL(edac_mc_handle_error); diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c index e9a28f576d1..f6a29b0eedc 100644 --- a/drivers/edac/edac_mc_sysfs.c +++ b/drivers/edac/edac_mc_sysfs.c @@ -144,25 +144,31 @@ static ssize_t csrow_ce_count_show(struct csrow_info *csrow, char *data, static ssize_t csrow_size_show(struct csrow_info *csrow, char *data, int private) { - return sprintf(data, "%u\n", PAGES_TO_MiB(csrow->nr_pages)); + int i; + u32 nr_pages = 0; + + for (i = 0; i < csrow->nr_channels; i++) + nr_pages += csrow->channels[i].dimm->nr_pages; + + return sprintf(data, "%u\n", PAGES_TO_MiB(nr_pages)); } static ssize_t csrow_mem_type_show(struct csrow_info *csrow, char *data, int private) { - return sprintf(data, "%s\n", mem_types[csrow->mtype]); + return sprintf(data, "%s\n", mem_types[csrow->channels[0].dimm->mtype]); } static ssize_t csrow_dev_type_show(struct csrow_info *csrow, char *data, int private) { - return sprintf(data, "%s\n", dev_types[csrow->dtype]); + return sprintf(data, "%s\n", dev_types[csrow->channels[0].dimm->dtype]); } static ssize_t csrow_edac_mode_show(struct csrow_info *csrow, char *data, int private) { - return sprintf(data, "%s\n", edac_caps[csrow->edac_mode]); + return sprintf(data, "%s\n", edac_caps[csrow->channels[0].dimm->edac_mode]); } /* show/store functions for DIMM Label attributes */ @@ -170,11 +176,11 @@ static ssize_t channel_dimm_label_show(struct csrow_info *csrow, char *data, int channel) { /* if field has not been initialized, there is nothing to send */ - if (!csrow->channels[channel].label[0]) + if (!csrow->channels[channel].dimm->label[0]) return 0; return snprintf(data, EDAC_MC_LABEL_LEN, "%s\n", - csrow->channels[channel].label); + csrow->channels[channel].dimm->label); } static ssize_t channel_dimm_label_store(struct csrow_info *csrow, @@ -184,8 +190,8 @@ static ssize_t channel_dimm_label_store(struct csrow_info *csrow, ssize_t max_size = 0; max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1); - strncpy(csrow->channels[channel].label, data, max_size); - csrow->channels[channel].label[max_size] = '\0'; + strncpy(csrow->channels[channel].dimm->label, data, max_size); + csrow->channels[channel].dimm->label[max_size] = '\0'; return max_size; } @@ -419,8 +425,8 @@ static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, mci->ue_noinfo_count = 0; mci->ce_noinfo_count = 0; - mci->ue_count = 0; - mci->ce_count = 0; + mci->ue_mc = 0; + mci->ce_mc = 0; for (row = 0; row < mci->nr_csrows; row++) { struct csrow_info *ri = &mci->csrows[row]; @@ -489,12 +495,12 @@ static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) /* default attribute files for the MCI object */ static ssize_t mci_ue_count_show(struct mem_ctl_info *mci, char *data) { - return sprintf(data, "%d\n", mci->ue_count); + return sprintf(data, "%d\n", mci->ue_mc); } static ssize_t mci_ce_count_show(struct mem_ctl_info *mci, char *data) { - return sprintf(data, "%d\n", mci->ce_count); + return sprintf(data, "%d\n", mci->ce_mc); } static ssize_t mci_ce_noinfo_show(struct mem_ctl_info *mci, char *data) @@ -519,16 +525,16 @@ static ssize_t mci_ctl_name_show(struct mem_ctl_info *mci, char *data) static ssize_t mci_size_mb_show(struct mem_ctl_info *mci, char *data) { - int total_pages, csrow_idx; + int total_pages = 0, csrow_idx, j; - for (total_pages = csrow_idx = 0; csrow_idx < mci->nr_csrows; - csrow_idx++) { + for (csrow_idx = 0; csrow_idx < mci->nr_csrows; csrow_idx++) { struct csrow_info *csrow = &mci->csrows[csrow_idx]; - if (!csrow->nr_pages) - continue; + for (j = 0; j < csrow->nr_channels; j++) { + struct dimm_info *dimm = csrow->channels[j].dimm; - total_pages += csrow->nr_pages; + total_pages += dimm->nr_pages; + } } return sprintf(data, "%u\n", PAGES_TO_MiB(total_pages)); @@ -900,7 +906,7 @@ static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci, */ int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) { - int i; + int i, j; int err; struct csrow_info *csrow; struct kobject *kobj_mci = &mci->edac_mci_kobj; @@ -934,10 +940,13 @@ int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) /* Make directories for each CSROW object under the mc<id> kobject */ for (i = 0; i < mci->nr_csrows; i++) { + int nr_pages = 0; + csrow = &mci->csrows[i]; + for (j = 0; j < csrow->nr_channels; j++) + nr_pages += csrow->channels[j].dimm->nr_pages; - /* Only expose populated CSROWs */ - if (csrow->nr_pages > 0) { + if (nr_pages > 0) { err = edac_create_csrow_object(mci, csrow, i); if (err) { debugf1("%s() failure: create csrow %d obj\n", @@ -949,12 +958,15 @@ int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) return 0; - /* CSROW error: backout what has already been registered, */ fail1: for (i--; i >= 0; i--) { - if (csrow->nr_pages > 0) { + int nr_pages = 0; + + csrow = &mci->csrows[i]; + for (j = 0; j < csrow->nr_channels; j++) + nr_pages += csrow->channels[j].dimm->nr_pages; + if (nr_pages > 0) kobject_put(&mci->csrows[i].kobj); - } } /* remove the mci instance's attributes, if any */ @@ -973,14 +985,20 @@ fail0: */ void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) { - int i; + struct csrow_info *csrow; + int i, j; debugf0("%s()\n", __func__); /* remove all csrow kobjects */ debugf4("%s() unregister this mci kobj\n", __func__); for (i = 0; i < mci->nr_csrows; i++) { - if (mci->csrows[i].nr_pages > 0) { + int nr_pages = 0; + + csrow = &mci->csrows[i]; + for (j = 0; j < csrow->nr_channels; j++) + nr_pages += csrow->channels[j].dimm->nr_pages; + if (nr_pages > 0) { debugf0("%s() unreg csrow-%d\n", __func__, i); kobject_put(&mci->csrows[i].kobj); } diff --git a/drivers/edac/edac_module.h b/drivers/edac/edac_module.h index 00f81b47a51..0ea7d14cb93 100644 --- a/drivers/edac/edac_module.h +++ b/drivers/edac/edac_module.h @@ -50,7 +50,7 @@ extern void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, unsigned long value); extern void edac_mc_reset_delay_period(int value); -extern void *edac_align_ptr(void *ptr, unsigned size); +extern void *edac_align_ptr(void **p, unsigned size, int n_elems); /* * EDAC PCI functions diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c index 63af1c5673d..f1ac8664988 100644 --- a/drivers/edac/edac_pci.c +++ b/drivers/edac/edac_pci.c @@ -42,13 +42,13 @@ struct edac_pci_ctl_info *edac_pci_alloc_ctl_info(unsigned int sz_pvt, const char *edac_pci_name) { struct edac_pci_ctl_info *pci; - void *pvt; + void *p = NULL, *pvt; unsigned int size; debugf1("%s()\n", __func__); - pci = (struct edac_pci_ctl_info *)0; - pvt = edac_align_ptr(&pci[1], sz_pvt); + pci = edac_align_ptr(&p, sizeof(*pci), 1); + pvt = edac_align_ptr(&p, 1, sz_pvt); size = ((unsigned long)pvt) + sz_pvt; /* Alloc the needed control struct memory */ diff --git a/drivers/edac/i3000_edac.c b/drivers/edac/i3000_edac.c index 277689a6884..8ad1744faac 100644 --- a/drivers/edac/i3000_edac.c +++ b/drivers/edac/i3000_edac.c @@ -245,7 +245,9 @@ static int i3000_process_error_info(struct mem_ctl_info *mci, return 1; if ((info->errsts ^ info->errsts2) & I3000_ERRSTS_BITS) { - edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, + "UE overwrote CE", "", NULL); info->errsts = info->errsts2; } @@ -256,10 +258,15 @@ static int i3000_process_error_info(struct mem_ctl_info *mci, row = edac_mc_find_csrow_by_page(mci, pfn); if (info->errsts & I3000_ERRSTS_UE) - edac_mc_handle_ue(mci, pfn, offset, row, "i3000 UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + pfn, offset, 0, + row, -1, -1, + "i3000 UE", "", NULL); else - edac_mc_handle_ce(mci, pfn, offset, info->derrsyn, row, - multi_chan ? channel : 0, "i3000 CE"); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + pfn, offset, info->derrsyn, + row, multi_chan ? channel : 0, -1, + "i3000 CE", "", NULL); return 1; } @@ -304,9 +311,10 @@ static int i3000_is_interleaved(const unsigned char *c0dra, static int i3000_probe1(struct pci_dev *pdev, int dev_idx) { int rc; - int i; + int i, j; struct mem_ctl_info *mci = NULL; - unsigned long last_cumul_size; + struct edac_mc_layer layers[2]; + unsigned long last_cumul_size, nr_pages; int interleaved, nr_channels; unsigned char dra[I3000_RANKS / 2], drb[I3000_RANKS]; unsigned char *c0dra = dra, *c1dra = &dra[I3000_RANKS_PER_CHANNEL / 2]; @@ -347,7 +355,14 @@ static int i3000_probe1(struct pci_dev *pdev, int dev_idx) */ interleaved = i3000_is_interleaved(c0dra, c1dra, c0drb, c1drb); nr_channels = interleaved ? 2 : 1; - mci = edac_mc_alloc(0, I3000_RANKS / nr_channels, nr_channels, 0); + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = I3000_RANKS / nr_channels; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = nr_channels; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); if (!mci) return -ENOMEM; @@ -386,19 +401,23 @@ static int i3000_probe1(struct pci_dev *pdev, int dev_idx) cumul_size <<= 1; debugf3("MC: %s(): (%d) cumul_size 0x%x\n", __func__, i, cumul_size); - if (cumul_size == last_cumul_size) { - csrow->mtype = MEM_EMPTY; + if (cumul_size == last_cumul_size) continue; - } csrow->first_page = last_cumul_size; csrow->last_page = cumul_size - 1; - csrow->nr_pages = cumul_size - last_cumul_size; + nr_pages = cumul_size - last_cumul_size; last_cumul_size = cumul_size; - csrow->grain = I3000_DEAP_GRAIN; - csrow->mtype = MEM_DDR2; - csrow->dtype = DEV_UNKNOWN; - csrow->edac_mode = EDAC_UNKNOWN; + + for (j = 0; j < nr_channels; j++) { + struct dimm_info *dimm = csrow->channels[j].dimm; + + dimm->nr_pages = nr_pages / nr_channels; + dimm->grain = I3000_DEAP_GRAIN; + dimm->mtype = MEM_DDR2; + dimm->dtype = DEV_UNKNOWN; + dimm->edac_mode = EDAC_UNKNOWN; + } } /* diff --git a/drivers/edac/i3200_edac.c b/drivers/edac/i3200_edac.c index 046808c6357..bbe43ef7182 100644 --- a/drivers/edac/i3200_edac.c +++ b/drivers/edac/i3200_edac.c @@ -23,6 +23,7 @@ #define PCI_DEVICE_ID_INTEL_3200_HB 0x29f0 +#define I3200_DIMMS 4 #define I3200_RANKS 8 #define I3200_RANKS_PER_CHANNEL 4 #define I3200_CHANNELS 2 @@ -217,21 +218,25 @@ static void i3200_process_error_info(struct mem_ctl_info *mci, return; if ((info->errsts ^ info->errsts2) & I3200_ERRSTS_BITS) { - edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, "UE overwrote CE", "", NULL); info->errsts = info->errsts2; } for (channel = 0; channel < nr_channels; channel++) { log = info->eccerrlog[channel]; if (log & I3200_ECCERRLOG_UE) { - edac_mc_handle_ue(mci, 0, 0, - eccerrlog_row(channel, log), - "i3200 UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + 0, 0, 0, + eccerrlog_row(channel, log), + -1, -1, + "i3000 UE", "", NULL); } else if (log & I3200_ECCERRLOG_CE) { - edac_mc_handle_ce(mci, 0, 0, - eccerrlog_syndrome(log), - eccerrlog_row(channel, log), 0, - "i3200 CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + 0, 0, eccerrlog_syndrome(log), + eccerrlog_row(channel, log), + -1, -1, + "i3000 UE", "", NULL); } } } @@ -319,9 +324,9 @@ static unsigned long drb_to_nr_pages( static int i3200_probe1(struct pci_dev *pdev, int dev_idx) { int rc; - int i; + int i, j; struct mem_ctl_info *mci = NULL; - unsigned long last_page; + struct edac_mc_layer layers[2]; u16 drbs[I3200_CHANNELS][I3200_RANKS_PER_CHANNEL]; bool stacked; void __iomem *window; @@ -336,8 +341,14 @@ static int i3200_probe1(struct pci_dev *pdev, int dev_idx) i3200_get_drbs(window, drbs); nr_channels = how_many_channels(pdev); - mci = edac_mc_alloc(sizeof(struct i3200_priv), I3200_RANKS, - nr_channels, 0); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = I3200_DIMMS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = nr_channels; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, + sizeof(struct i3200_priv)); if (!mci) return -ENOMEM; @@ -366,7 +377,6 @@ static int i3200_probe1(struct pci_dev *pdev, int dev_idx) * cumulative; the last one will contain the total memory * contained in all ranks. */ - last_page = -1UL; for (i = 0; i < mci->nr_csrows; i++) { unsigned long nr_pages; struct csrow_info *csrow = &mci->csrows[i]; @@ -375,20 +385,18 @@ static int i3200_probe1(struct pci_dev *pdev, int dev_idx) i / I3200_RANKS_PER_CHANNEL, i % I3200_RANKS_PER_CHANNEL); - if (nr_pages == 0) { - csrow->mtype = MEM_EMPTY; + if (nr_pages == 0) continue; - } - csrow->first_page = last_page + 1; - last_page += nr_pages; - csrow->last_page = last_page; - csrow->nr_pages = nr_pages; + for (j = 0; j < nr_channels; j++) { + struct dimm_info *dimm = csrow->channels[j].dimm; - csrow->grain = nr_pages << PAGE_SHIFT; - csrow->mtype = MEM_DDR2; - csrow->dtype = DEV_UNKNOWN; - csrow->edac_mode = EDAC_UNKNOWN; + dimm->nr_pages = nr_pages / nr_channels; + dimm->grain = nr_pages << PAGE_SHIFT; + dimm->mtype = MEM_DDR2; + dimm->dtype = DEV_UNKNOWN; + dimm->edac_mode = EDAC_UNKNOWN; + } } i3200_clear_error_info(mci); diff --git a/drivers/edac/i5000_edac.c b/drivers/edac/i5000_edac.c index a2680d8e744..11ea835f155 100644 --- a/drivers/edac/i5000_edac.c +++ b/drivers/edac/i5000_edac.c @@ -270,7 +270,8 @@ #define MTR3 0x8C #define NUM_MTRS 4 -#define CHANNELS_PER_BRANCH (2) +#define CHANNELS_PER_BRANCH 2 +#define MAX_BRANCHES 2 /* Defines to extract the vaious fields from the * MTRx - Memory Technology Registers @@ -473,7 +474,6 @@ static void i5000_process_fatal_error_info(struct mem_ctl_info *mci, char msg[EDAC_MC_LABEL_LEN + 1 + 160]; char *specific = NULL; u32 allErrors; - int branch; int channel; int bank; int rank; @@ -485,8 +485,7 @@ static void i5000_process_fatal_error_info(struct mem_ctl_info *mci, if (!allErrors) return; /* if no error, return now */ - branch = EXTRACT_FBDCHAN_INDX(info->ferr_fat_fbd); - channel = branch; + channel = EXTRACT_FBDCHAN_INDX(info->ferr_fat_fbd); /* Use the NON-Recoverable macros to extract data */ bank = NREC_BANK(info->nrecmema); @@ -495,9 +494,9 @@ static void i5000_process_fatal_error_info(struct mem_ctl_info *mci, ras = NREC_RAS(info->nrecmemb); cas = NREC_CAS(info->nrecmemb); - debugf0("\t\tCSROW= %d Channels= %d,%d (Branch= %d " - "DRAM Bank= %d rdwr= %s ras= %d cas= %d)\n", - rank, channel, channel + 1, branch >> 1, bank, + debugf0("\t\tCSROW= %d Channel= %d " + "(DRAM Bank= %d rdwr= %s ras= %d cas= %d)\n", + rank, channel, bank, rdwr ? "Write" : "Read", ras, cas); /* Only 1 bit will be on */ @@ -533,13 +532,14 @@ static void i5000_process_fatal_error_info(struct mem_ctl_info *mci, /* Form out message */ snprintf(msg, sizeof(msg), - "(Branch=%d DRAM-Bank=%d RDWR=%s RAS=%d CAS=%d " - "FATAL Err=0x%x (%s))", - branch >> 1, bank, rdwr ? "Write" : "Read", ras, cas, - allErrors, specific); + "Bank=%d RAS=%d CAS=%d FATAL Err=0x%x (%s)", + bank, ras, cas, allErrors, specific); /* Call the helper to output message */ - edac_mc_handle_fbd_ue(mci, rank, channel, channel + 1, msg); + edac_mc_handle_error(HW_EVENT_ERR_FATAL, mci, 0, 0, 0, + channel >> 1, channel & 1, rank, + rdwr ? "Write error" : "Read error", + msg, NULL); } /* @@ -633,13 +633,14 @@ static void i5000_process_nonfatal_error_info(struct mem_ctl_info *mci, /* Form out message */ snprintf(msg, sizeof(msg), - "(Branch=%d DRAM-Bank=%d RDWR=%s RAS=%d " - "CAS=%d, UE Err=0x%x (%s))", - branch >> 1, bank, rdwr ? "Write" : "Read", ras, cas, - ue_errors, specific); + "Rank=%d Bank=%d RAS=%d CAS=%d, UE Err=0x%x (%s)", + rank, bank, ras, cas, ue_errors, specific); /* Call the helper to output message */ - edac_mc_handle_fbd_ue(mci, rank, channel, channel + 1, msg); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + channel >> 1, -1, rank, + rdwr ? "Write error" : "Read error", + msg, NULL); } /* Check correctable errors */ @@ -685,13 +686,16 @@ static void i5000_process_nonfatal_error_info(struct mem_ctl_info *mci, /* Form out message */ snprintf(msg, sizeof(msg), - "(Branch=%d DRAM-Bank=%d RDWR=%s RAS=%d " + "Rank=%d Bank=%d RDWR=%s RAS=%d " "CAS=%d, CE Err=0x%x (%s))", branch >> 1, bank, rdwr ? "Write" : "Read", ras, cas, ce_errors, specific); /* Call the helper to output message */ - edac_mc_handle_fbd_ce(mci, rank, channel, msg); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 0, 0, 0, + channel >> 1, channel % 2, rank, + rdwr ? "Write error" : "Read error", + msg, NULL); } if (!misc_messages) @@ -731,11 +735,12 @@ static void i5000_process_nonfatal_error_info(struct mem_ctl_info *mci, /* Form out message */ snprintf(msg, sizeof(msg), - "(Branch=%d Err=%#x (%s))", branch >> 1, - misc_errors, specific); + "Err=%#x (%s)", misc_errors, specific); /* Call the helper to output message */ - edac_mc_handle_fbd_ce(mci, 0, 0, msg); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 0, 0, 0, + branch >> 1, -1, -1, + "Misc error", msg, NULL); } } @@ -956,14 +961,14 @@ static int determine_amb_present_reg(struct i5000_pvt *pvt, int channel) * * return the proper MTR register as determine by the csrow and channel desired */ -static int determine_mtr(struct i5000_pvt *pvt, int csrow, int channel) +static int determine_mtr(struct i5000_pvt *pvt, int slot, int channel) { int mtr; if (channel < CHANNELS_PER_BRANCH) - mtr = pvt->b0_mtr[csrow >> 1]; + mtr = pvt->b0_mtr[slot]; else - mtr = pvt->b1_mtr[csrow >> 1]; + mtr = pvt->b1_mtr[slot]; return mtr; } @@ -988,37 +993,34 @@ static void decode_mtr(int slot_row, u16 mtr) debugf2("\t\tNUMCOL: %s\n", numcol_toString[MTR_DIMM_COLS(mtr)]); } -static void handle_channel(struct i5000_pvt *pvt, int csrow, int channel, +static void handle_channel(struct i5000_pvt *pvt, int slot, int channel, struct i5000_dimm_info *dinfo) { int mtr; int amb_present_reg; int addrBits; - mtr = determine_mtr(pvt, csrow, channel); + mtr = determine_mtr(pvt, slot, channel); if (MTR_DIMMS_PRESENT(mtr)) { amb_present_reg = determine_amb_present_reg(pvt, channel); - /* Determine if there is a DIMM present in this DIMM slot */ - if (amb_present_reg & (1 << (csrow >> 1))) { + /* Determine if there is a DIMM present in this DIMM slot */ + if (amb_present_reg) { dinfo->dual_rank = MTR_DIMM_RANK(mtr); - if (!((dinfo->dual_rank == 0) && - ((csrow & 0x1) == 0x1))) { - /* Start with the number of bits for a Bank - * on the DRAM */ - addrBits = MTR_DRAM_BANKS_ADDR_BITS(mtr); - /* Add thenumber of ROW bits */ - addrBits += MTR_DIMM_ROWS_ADDR_BITS(mtr); - /* add the number of COLUMN bits */ - addrBits += MTR_DIMM_COLS_ADDR_BITS(mtr); - - addrBits += 6; /* add 64 bits per DIMM */ - addrBits -= 20; /* divide by 2^^20 */ - addrBits -= 3; /* 8 bits per bytes */ - - dinfo->megabytes = 1 << addrBits; - } + /* Start with the number of bits for a Bank + * on the DRAM */ + addrBits = MTR_DRAM_BANKS_ADDR_BITS(mtr); + /* Add the number of ROW bits */ + addrBits += MTR_DIMM_ROWS_ADDR_BITS(mtr); + /* add the number of COLUMN bits */ + addrBits += MTR_DIMM_COLS_ADDR_BITS(mtr); + + addrBits += 6; /* add 64 bits per DIMM */ + addrBits -= 20; /* divide by 2^^20 */ + addrBits -= 3; /* 8 bits per bytes */ + + dinfo->megabytes = 1 << addrBits; } } } @@ -1032,10 +1034,9 @@ static void handle_channel(struct i5000_pvt *pvt, int csrow, int channel, static void calculate_dimm_size(struct i5000_pvt *pvt) { struct i5000_dimm_info *dinfo; - int csrow, max_csrows; + int slot, channel, branch; char *p, *mem_buffer; int space, n; - int channel; /* ================= Generate some debug output ================= */ space = PAGE_SIZE; @@ -1046,22 +1047,17 @@ static void calculate_dimm_size(struct i5000_pvt *pvt) return; } - n = snprintf(p, space, "\n"); - p += n; - space -= n; - - /* Scan all the actual CSROWS (which is # of DIMMS * 2) + /* Scan all the actual slots * and calculate the information for each DIMM - * Start with the highest csrow first, to display it first - * and work toward the 0th csrow + * Start with the highest slot first, to display it first + * and work toward the 0th slot */ - max_csrows = pvt->maxdimmperch * 2; - for (csrow = max_csrows - 1; csrow >= 0; csrow--) { + for (slot = pvt->maxdimmperch - 1; slot >= 0; slot--) { - /* on an odd csrow, first output a 'boundary' marker, + /* on an odd slot, first output a 'boundary' marker, * then reset the message buffer */ - if (csrow & 0x1) { - n = snprintf(p, space, "---------------------------" + if (slot & 0x1) { + n = snprintf(p, space, "--------------------------" "--------------------------------"); p += n; space -= n; @@ -1069,30 +1065,39 @@ static void calculate_dimm_size(struct i5000_pvt *pvt) p = mem_buffer; space = PAGE_SIZE; } - n = snprintf(p, space, "csrow %2d ", csrow); + n = snprintf(p, space, "slot %2d ", slot); p += n; space -= n; for (channel = 0; channel < pvt->maxch; channel++) { - dinfo = &pvt->dimm_info[csrow][channel]; - handle_channel(pvt, csrow, channel, dinfo); - n = snprintf(p, space, "%4d MB | ", dinfo->megabytes); + dinfo = &pvt->dimm_info[slot][channel]; + handle_channel(pvt, slot, channel, dinfo); + if (dinfo->megabytes) + n = snprintf(p, space, "%4d MB %dR| ", + dinfo->megabytes, dinfo->dual_rank + 1); + else + n = snprintf(p, space, "%4d MB | ", 0); p += n; space -= n; } - n = snprintf(p, space, "\n"); p += n; space -= n; + debugf2("%s\n", mem_buffer); + p = mem_buffer; + space = PAGE_SIZE; } /* Output the last bottom 'boundary' marker */ - n = snprintf(p, space, "---------------------------" - "--------------------------------\n"); + n = snprintf(p, space, "--------------------------" + "--------------------------------"); p += n; space -= n; + debugf2("%s\n", mem_buffer); + p = mem_buffer; + space = PAGE_SIZE; /* now output the 'channel' labels */ - n = snprintf(p, space, " "); + n = snprintf(p, space, " "); p += n; space -= n; for (channel = 0; channel < pvt->maxch; channel++) { @@ -1100,9 +1105,17 @@ static void calculate_dimm_size(struct i5000_pvt *pvt) p += n; space -= n; } - n = snprintf(p, space, "\n"); + debugf2("%s\n", mem_buffer); + p = mem_buffer; + space = PAGE_SIZE; + + n = snprintf(p, space, " "); p += n; - space -= n; + for (branch = 0; branch < MAX_BRANCHES; branch++) { + n = snprintf(p, space, " branch %d | ", branch); + p += n; + space -= n; + } /* output the last message and free buffer */ debugf2("%s\n", mem_buffer); @@ -1235,13 +1248,13 @@ static void i5000_get_mc_regs(struct mem_ctl_info *mci) static int i5000_init_csrows(struct mem_ctl_info *mci) { struct i5000_pvt *pvt; - struct csrow_info *p_csrow; + struct dimm_info *dimm; int empty, channel_count; int max_csrows; - int mtr, mtr1; + int mtr; int csrow_megs; int channel; - int csrow; + int slot; pvt = mci->pvt_info; @@ -1250,43 +1263,40 @@ static int i5000_init_csrows(struct mem_ctl_info *mci) empty = 1; /* Assume NO memory */ - for (csrow = 0; csrow < max_csrows; csrow++) { - p_csrow = &mci->csrows[csrow]; - - p_csrow->csrow_idx = csrow; - - /* use branch 0 for the basis */ - mtr = pvt->b0_mtr[csrow >> 1]; - mtr1 = pvt->b1_mtr[csrow >> 1]; - - /* if no DIMMS on this row, continue */ - if (!MTR_DIMMS_PRESENT(mtr) && !MTR_DIMMS_PRESENT(mtr1)) - continue; + /* + * FIXME: The memory layout used to map slot/channel into the + * real memory architecture is weird: branch+slot are "csrows" + * and channel is channel. That required an extra array (dimm_info) + * to map the dimms. A good cleanup would be to remove this array, + * and do a loop here with branch, channel, slot + */ + for (slot = 0; slot < max_csrows; slot++) { + for (channel = 0; channel < pvt->maxch; channel++) { - /* FAKE OUT VALUES, FIXME */ - p_csrow->first_page = 0 + csrow * 20; - p_csrow->last_page = 9 + csrow * 20; - p_csrow->page_mask = 0xFFF; + mtr = determine_mtr(pvt, slot, channel); - p_csrow->grain = 8; + if (!MTR_DIMMS_PRESENT(mtr)) + continue; - csrow_megs = 0; - for (channel = 0; channel < pvt->maxch; channel++) { - csrow_megs += pvt->dimm_info[csrow][channel].megabytes; - } + dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, + channel / MAX_BRANCHES, + channel % MAX_BRANCHES, slot); - p_csrow->nr_pages = csrow_megs << 8; + csrow_megs = pvt->dimm_info[slot][channel].megabytes; + dimm->grain = 8; - /* Assume DDR2 for now */ - p_csrow->mtype = MEM_FB_DDR2; + /* Assume DDR2 for now */ + dimm->mtype = MEM_FB_DDR2; - /* ask what device type on this row */ - if (MTR_DRAM_WIDTH(mtr)) - p_csrow->dtype = DEV_X8; - else - p_csrow->dtype = DEV_X4; + /* ask what device type on this row */ + if (MTR_DRAM_WIDTH(mtr)) + dimm->dtype = DEV_X8; + else + dimm->dtype = DEV_X4; - p_csrow->edac_mode = EDAC_S8ECD8ED; + dimm->edac_mode = EDAC_S8ECD8ED; + dimm->nr_pages = csrow_megs << 8; + } empty = 0; } @@ -1317,7 +1327,7 @@ static void i5000_enable_error_reporting(struct mem_ctl_info *mci) } /* - * i5000_get_dimm_and_channel_counts(pdev, &num_csrows, &num_channels) + * i5000_get_dimm_and_channel_counts(pdev, &nr_csrows, &num_channels) * * ask the device how many channels are present and how many CSROWS * as well @@ -1332,7 +1342,7 @@ static void i5000_get_dimm_and_channel_counts(struct pci_dev *pdev, * supported on this memory controller */ pci_read_config_byte(pdev, MAXDIMMPERCH, &value); - *num_dimms_per_channel = (int)value *2; + *num_dimms_per_channel = (int)value; pci_read_config_byte(pdev, MAXCH, &value); *num_channels = (int)value; @@ -1348,10 +1358,10 @@ static void i5000_get_dimm_and_channel_counts(struct pci_dev *pdev, static int i5000_probe1(struct pci_dev *pdev, int dev_idx) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[3]; struct i5000_pvt *pvt; int num_channels; int num_dimms_per_channel; - int num_csrows; debugf0("MC: %s: %s(), pdev bus %u dev=0x%x fn=0x%x\n", __FILE__, __func__, @@ -1377,14 +1387,22 @@ static int i5000_probe1(struct pci_dev *pdev, int dev_idx) */ i5000_get_dimm_and_channel_counts(pdev, &num_dimms_per_channel, &num_channels); - num_csrows = num_dimms_per_channel * 2; - debugf0("MC: %s(): Number of - Channels= %d DIMMS= %d CSROWS= %d\n", - __func__, num_channels, num_dimms_per_channel, num_csrows); + debugf0("MC: %s(): Number of Branches=2 Channels= %d DIMMS= %d\n", + __func__, num_channels, num_dimms_per_channel); /* allocate a new MC control structure */ - mci = edac_mc_alloc(sizeof(*pvt), num_csrows, num_channels, 0); + layers[0].type = EDAC_MC_LAYER_BRANCH; + layers[0].size = MAX_BRANCHES; + layers[0].is_virt_csrow = false; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = num_channels / MAX_BRANCHES; + layers[1].is_virt_csrow = false; + layers[2].type = EDAC_MC_LAYER_SLOT; + layers[2].size = num_dimms_per_channel; + layers[2].is_virt_csrow = true; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (mci == NULL) return -ENOMEM; diff --git a/drivers/edac/i5100_edac.c b/drivers/edac/i5100_edac.c index d500749464e..e9e7c2a29dc 100644 --- a/drivers/edac/i5100_edac.c +++ b/drivers/edac/i5100_edac.c @@ -14,6 +14,11 @@ * rows for each respective channel are laid out one after another, * the first half belonging to channel 0, the second half belonging * to channel 1. + * + * This driver is for DDR2 DIMMs, and it uses chip select to select among the + * several ranks. However, instead of showing memories as ranks, it outputs + * them as DIMM's. An internal table creates the association between ranks + * and DIMM's. */ #include <linux/module.h> #include <linux/init.h> @@ -410,14 +415,6 @@ static int i5100_csrow_to_chan(const struct mem_ctl_info *mci, int csrow) return csrow / priv->ranksperchan; } -static unsigned i5100_rank_to_csrow(const struct mem_ctl_info *mci, - int chan, int rank) -{ - const struct i5100_priv *priv = mci->pvt_info; - - return chan * priv->ranksperchan + rank; -} - static void i5100_handle_ce(struct mem_ctl_info *mci, int chan, unsigned bank, @@ -427,17 +424,17 @@ static void i5100_handle_ce(struct mem_ctl_info *mci, unsigned ras, const char *msg) { - const int csrow = i5100_rank_to_csrow(mci, chan, rank); + char detail[80]; - printk(KERN_ERR - "CE chan %d, bank %u, rank %u, syndrome 0x%lx, " - "cas %u, ras %u, csrow %u, label \"%s\": %s\n", - chan, bank, rank, syndrome, cas, ras, - csrow, mci->csrows[csrow].channels[0].label, msg); + /* Form out message */ + snprintf(detail, sizeof(detail), + "bank %u, cas %u, ras %u\n", + bank, cas, ras); - mci->ce_count++; - mci->csrows[csrow].ce_count++; - mci->csrows[csrow].channels[0].ce_count++; + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + 0, 0, syndrome, + chan, rank, -1, + msg, detail, NULL); } static void i5100_handle_ue(struct mem_ctl_info *mci, @@ -449,16 +446,17 @@ static void i5100_handle_ue(struct mem_ctl_info *mci, unsigned ras, const char *msg) { - const int csrow = i5100_rank_to_csrow(mci, chan, rank); + char detail[80]; - printk(KERN_ERR - "UE chan %d, bank %u, rank %u, syndrome 0x%lx, " - "cas %u, ras %u, csrow %u, label \"%s\": %s\n", - chan, bank, rank, syndrome, cas, ras, - csrow, mci->csrows[csrow].channels[0].label, msg); + /* Form out message */ + snprintf(detail, sizeof(detail), + "bank %u, cas %u, ras %u\n", + bank, cas, ras); - mci->ue_count++; - mci->csrows[csrow].ue_count++; + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + 0, 0, syndrome, + chan, rank, -1, + msg, detail, NULL); } static void i5100_read_log(struct mem_ctl_info *mci, int chan, @@ -835,10 +833,10 @@ static void __devinit i5100_init_interleaving(struct pci_dev *pdev, static void __devinit i5100_init_csrows(struct mem_ctl_info *mci) { int i; - unsigned long total_pages = 0UL; struct i5100_priv *priv = mci->pvt_info; - for (i = 0; i < mci->nr_csrows; i++) { + for (i = 0; i < mci->tot_dimms; i++) { + struct dimm_info *dimm; const unsigned long npages = i5100_npages(mci, i); const unsigned chan = i5100_csrow_to_chan(mci, i); const unsigned rank = i5100_csrow_to_rank(mci, i); @@ -846,33 +844,23 @@ static void __devinit i5100_init_csrows(struct mem_ctl_info *mci) if (!npages) continue; - /* - * FIXME: these two are totally bogus -- I don't see how to - * map them correctly to this structure... - */ - mci->csrows[i].first_page = total_pages; - mci->csrows[i].last_page = total_pages + npages - 1; - mci->csrows[i].page_mask = 0UL; - - mci->csrows[i].nr_pages = npages; - mci->csrows[i].grain = 32; - mci->csrows[i].csrow_idx = i; - mci->csrows[i].dtype = - (priv->mtr[chan][rank].width == 4) ? DEV_X4 : DEV_X8; - mci->csrows[i].ue_count = 0; - mci->csrows[i].ce_count = 0; - mci->csrows[i].mtype = MEM_RDDR2; - mci->csrows[i].edac_mode = EDAC_SECDED; - mci->csrows[i].mci = mci; - mci->csrows[i].nr_channels = 1; - mci->csrows[i].channels[0].chan_idx = 0; - mci->csrows[i].channels[0].ce_count = 0; - mci->csrows[i].channels[0].csrow = mci->csrows + i; - snprintf(mci->csrows[i].channels[0].label, - sizeof(mci->csrows[i].channels[0].label), - "DIMM%u", i5100_rank_to_slot(mci, chan, rank)); - - total_pages += npages; + dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, + chan, rank, 0); + + dimm->nr_pages = npages; + if (npages) { + dimm->grain = 32; + dimm->dtype = (priv->mtr[chan][rank].width == 4) ? + DEV_X4 : DEV_X8; + dimm->mtype = MEM_RDDR2; + dimm->edac_mode = EDAC_SECDED; + snprintf(dimm->label, sizeof(dimm->label), + "DIMM%u", + i5100_rank_to_slot(mci, chan, rank)); + } + + debugf2("dimm channel %d, rank %d, size %ld\n", + chan, rank, (long)PAGES_TO_MiB(npages)); } } @@ -881,6 +869,7 @@ static int __devinit i5100_init_one(struct pci_dev *pdev, { int rc; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct i5100_priv *priv; struct pci_dev *ch0mm, *ch1mm; int ret = 0; @@ -941,7 +930,14 @@ static int __devinit i5100_init_one(struct pci_dev *pdev, goto bail_ch1; } - mci = edac_mc_alloc(sizeof(*priv), ranksperch * 2, 1, 0); + layers[0].type = EDAC_MC_LAYER_CHANNEL; + layers[0].size = 2; + layers[0].is_virt_csrow = false; + layers[1].type = EDAC_MC_LAYER_SLOT; + layers[1].size = ranksperch; + layers[1].is_virt_csrow = true; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, + sizeof(*priv)); if (!mci) { ret = -ENOMEM; goto bail_disable_ch1; diff --git a/drivers/edac/i5400_edac.c b/drivers/edac/i5400_edac.c index 1869a1018fb..6640c29e188 100644 --- a/drivers/edac/i5400_edac.c +++ b/drivers/edac/i5400_edac.c @@ -18,6 +18,10 @@ * Intel 5400 Chipset Memory Controller Hub (MCH) - Datasheet * http://developer.intel.com/design/chipsets/datashts/313070.htm * + * This Memory Controller manages DDR2 FB-DIMMs. It has 2 branches, each with + * 2 channels operating in lockstep no-mirror mode. Each channel can have up to + * 4 dimm's, each with up to 8GB. + * */ #include <linux/module.h> @@ -44,12 +48,10 @@ edac_mc_chipset_printk(mci, level, "i5400", fmt, ##arg) /* Limits for i5400 */ -#define NUM_MTRS_PER_BRANCH 4 +#define MAX_BRANCHES 2 #define CHANNELS_PER_BRANCH 2 -#define MAX_DIMMS_PER_CHANNEL NUM_MTRS_PER_BRANCH -#define MAX_CHANNELS 4 -/* max possible csrows per channel */ -#define MAX_CSROWS (MAX_DIMMS_PER_CHANNEL) +#define DIMMS_PER_CHANNEL 4 +#define MAX_CHANNELS (MAX_BRANCHES * CHANNELS_PER_BRANCH) /* Device 16, * Function 0: System Address @@ -347,16 +349,16 @@ struct i5400_pvt { u16 mir0, mir1; - u16 b0_mtr[NUM_MTRS_PER_BRANCH]; /* Memory Technlogy Reg */ + u16 b0_mtr[DIMMS_PER_CHANNEL]; /* Memory Technlogy Reg */ u16 b0_ambpresent0; /* Branch 0, Channel 0 */ u16 b0_ambpresent1; /* Brnach 0, Channel 1 */ - u16 b1_mtr[NUM_MTRS_PER_BRANCH]; /* Memory Technlogy Reg */ + u16 b1_mtr[DIMMS_PER_CHANNEL]; /* Memory Technlogy Reg */ u16 b1_ambpresent0; /* Branch 1, Channel 8 */ u16 b1_ambpresent1; /* Branch 1, Channel 1 */ /* DIMM information matrix, allocating architecture maximums */ - struct i5400_dimm_info dimm_info[MAX_CSROWS][MAX_CHANNELS]; + struct i5400_dimm_info dimm_info[DIMMS_PER_CHANNEL][MAX_CHANNELS]; /* Actual values for this controller */ int maxch; /* Max channels */ @@ -532,13 +534,15 @@ static void i5400_proccess_non_recoverable_info(struct mem_ctl_info *mci, int ras, cas; int errnum; char *type = NULL; + enum hw_event_mc_err_type tp_event = HW_EVENT_ERR_UNCORRECTED; if (!allErrors) return; /* if no error, return now */ - if (allErrors & ERROR_FAT_MASK) + if (allErrors & ERROR_FAT_MASK) { type = "FATAL"; - else if (allErrors & FERR_NF_UNCORRECTABLE) + tp_event = HW_EVENT_ERR_FATAL; + } else if (allErrors & FERR_NF_UNCORRECTABLE) type = "NON-FATAL uncorrected"; else type = "NON-FATAL recoverable"; @@ -556,7 +560,7 @@ static void i5400_proccess_non_recoverable_info(struct mem_ctl_info *mci, ras = nrec_ras(info); cas = nrec_cas(info); - debugf0("\t\tCSROW= %d Channels= %d,%d (Branch= %d " + debugf0("\t\tDIMM= %d Channels= %d,%d (Branch= %d " "DRAM Bank= %d Buffer ID = %d rdwr= %s ras= %d cas= %d)\n", rank, channel, channel + 1, branch >> 1, bank, buf_id, rdwr_str(rdwr), ras, cas); @@ -566,13 +570,13 @@ static void i5400_proccess_non_recoverable_info(struct mem_ctl_info *mci, /* Form out message */ snprintf(msg, sizeof(msg), - "%s (Branch=%d DRAM-Bank=%d Buffer ID = %d RDWR=%s " - "RAS=%d CAS=%d %s Err=0x%lx (%s))", - type, branch >> 1, bank, buf_id, rdwr_str(rdwr), ras, cas, - type, allErrors, error_name[errnum]); + "Bank=%d Buffer ID = %d RAS=%d CAS=%d Err=0x%lx (%s)", + bank, buf_id, ras, cas, allErrors, error_name[errnum]); - /* Call the helper to output message */ - edac_mc_handle_fbd_ue(mci, rank, channel, channel + 1, msg); + edac_mc_handle_error(tp_event, mci, 0, 0, 0, + branch >> 1, -1, rank, + rdwr ? "Write error" : "Read error", + msg, NULL); } /* @@ -630,7 +634,7 @@ static void i5400_process_nonfatal_error_info(struct mem_ctl_info *mci, /* Only 1 bit will be on */ errnum = find_first_bit(&allErrors, ARRAY_SIZE(error_name)); - debugf0("\t\tCSROW= %d Channel= %d (Branch %d " + debugf0("\t\tDIMM= %d Channel= %d (Branch %d " "DRAM Bank= %d rdwr= %s ras= %d cas= %d)\n", rank, channel, branch >> 1, bank, rdwr_str(rdwr), ras, cas); @@ -642,8 +646,10 @@ static void i5400_process_nonfatal_error_info(struct mem_ctl_info *mci, branch >> 1, bank, rdwr_str(rdwr), ras, cas, allErrors, error_name[errnum]); - /* Call the helper to output message */ - edac_mc_handle_fbd_ce(mci, rank, channel, msg); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 0, 0, 0, + branch >> 1, channel % 2, rank, + rdwr ? "Write error" : "Read error", + msg, NULL); return; } @@ -831,8 +837,8 @@ static int i5400_get_devices(struct mem_ctl_info *mci, int dev_idx) /* * determine_amb_present * - * the information is contained in NUM_MTRS_PER_BRANCH different - * registers determining which of the NUM_MTRS_PER_BRANCH requires + * the information is contained in DIMMS_PER_CHANNEL different + * registers determining which of the DIMMS_PER_CHANNEL requires * knowing which channel is in question * * 2 branches, each with 2 channels @@ -861,11 +867,11 @@ static int determine_amb_present_reg(struct i5400_pvt *pvt, int channel) } /* - * determine_mtr(pvt, csrow, channel) + * determine_mtr(pvt, dimm, channel) * - * return the proper MTR register as determine by the csrow and desired channel + * return the proper MTR register as determine by the dimm and desired channel */ -static int determine_mtr(struct i5400_pvt *pvt, int csrow, int channel) +static int determine_mtr(struct i5400_pvt *pvt, int dimm, int channel) { int mtr; int n; @@ -873,11 +879,11 @@ static int determine_mtr(struct i5400_pvt *pvt, int csrow, int channel) /* There is one MTR for each slot pair of FB-DIMMs, Each slot pair may be at branch 0 or branch 1. */ - n = csrow; + n = dimm; - if (n >= NUM_MTRS_PER_BRANCH) { - debugf0("ERROR: trying to access an invalid csrow: %d\n", - csrow); + if (n >= DIMMS_PER_CHANNEL) { + debugf0("ERROR: trying to access an invalid dimm: %d\n", + dimm); return 0; } @@ -913,19 +919,19 @@ static void decode_mtr(int slot_row, u16 mtr) debugf2("\t\tNUMCOL: %s\n", numcol_toString[MTR_DIMM_COLS(mtr)]); } -static void handle_channel(struct i5400_pvt *pvt, int csrow, int channel, +static void handle_channel(struct i5400_pvt *pvt, int dimm, int channel, struct i5400_dimm_info *dinfo) { int mtr; int amb_present_reg; int addrBits; - mtr = determine_mtr(pvt, csrow, channel); + mtr = determine_mtr(pvt, dimm, channel); if (MTR_DIMMS_PRESENT(mtr)) { amb_present_reg = determine_amb_present_reg(pvt, channel); /* Determine if there is a DIMM present in this DIMM slot */ - if (amb_present_reg & (1 << csrow)) { + if (amb_present_reg & (1 << dimm)) { /* Start with the number of bits for a Bank * on the DRAM */ addrBits = MTR_DRAM_BANKS_ADDR_BITS(mtr); @@ -954,10 +960,10 @@ static void handle_channel(struct i5400_pvt *pvt, int csrow, int channel, static void calculate_dimm_size(struct i5400_pvt *pvt) { struct i5400_dimm_info *dinfo; - int csrow, max_csrows; + int dimm, max_dimms; char *p, *mem_buffer; int space, n; - int channel; + int channel, branch; /* ================= Generate some debug output ================= */ space = PAGE_SIZE; @@ -968,32 +974,32 @@ static void calculate_dimm_size(struct i5400_pvt *pvt) return; } - /* Scan all the actual CSROWS + /* Scan all the actual DIMMS * and calculate the information for each DIMM - * Start with the highest csrow first, to display it first - * and work toward the 0th csrow + * Start with the highest dimm first, to display it first + * and work toward the 0th dimm */ - max_csrows = pvt->maxdimmperch; - for (csrow = max_csrows - 1; csrow >= 0; csrow--) { + max_dimms = pvt->maxdimmperch; + for (dimm = max_dimms - 1; dimm >= 0; dimm--) { - /* on an odd csrow, first output a 'boundary' marker, + /* on an odd dimm, first output a 'boundary' marker, * then reset the message buffer */ - if (csrow & 0x1) { + if (dimm & 0x1) { n = snprintf(p, space, "---------------------------" - "--------------------------------"); + "-------------------------------"); p += n; space -= n; debugf2("%s\n", mem_buffer); p = mem_buffer; space = PAGE_SIZE; } - n = snprintf(p, space, "csrow %2d ", csrow); + n = snprintf(p, space, "dimm %2d ", dimm); p += n; space -= n; for (channel = 0; channel < pvt->maxch; channel++) { - dinfo = &pvt->dimm_info[csrow][channel]; - handle_channel(pvt, csrow, channel, dinfo); + dinfo = &pvt->dimm_info[dimm][channel]; + handle_channel(pvt, dimm, channel, dinfo); n = snprintf(p, space, "%4d MB | ", dinfo->megabytes); p += n; space -= n; @@ -1005,7 +1011,7 @@ static void calculate_dimm_size(struct i5400_pvt *pvt) /* Output the last bottom 'boundary' marker */ n = snprintf(p, space, "---------------------------" - "--------------------------------"); + "-------------------------------"); p += n; space -= n; debugf2("%s\n", mem_buffer); @@ -1013,7 +1019,7 @@ static void calculate_dimm_size(struct i5400_pvt *pvt) space = PAGE_SIZE; /* now output the 'channel' labels */ - n = snprintf(p, space, " "); + n = snprintf(p, space, " "); p += n; space -= n; for (channel = 0; channel < pvt->maxch; channel++) { @@ -1022,6 +1028,19 @@ static void calculate_dimm_size(struct i5400_pvt *pvt) space -= n; } + space -= n; + debugf2("%s\n", mem_buffer); + p = mem_buffer; + space = PAGE_SIZE; + + n = snprintf(p, space, " "); + p += n; + for (branch = 0; branch < MAX_BRANCHES; branch++) { + n = snprintf(p, space, " branch %d | ", branch); + p += n; + space -= n; + } + /* output the last message and free buffer */ debugf2("%s\n", mem_buffer); kfree(mem_buffer); @@ -1080,7 +1099,7 @@ static void i5400_get_mc_regs(struct mem_ctl_info *mci) debugf2("MIR1: limit= 0x%x WAY1= %u WAY0= %x\n", limit, way1, way0); /* Get the set of MTR[0-3] regs by each branch */ - for (slot_row = 0; slot_row < NUM_MTRS_PER_BRANCH; slot_row++) { + for (slot_row = 0; slot_row < DIMMS_PER_CHANNEL; slot_row++) { int where = MTR0 + (slot_row * sizeof(u16)); /* Branch 0 set of MTR registers */ @@ -1105,7 +1124,7 @@ static void i5400_get_mc_regs(struct mem_ctl_info *mci) /* Read and dump branch 0's MTRs */ debugf2("\nMemory Technology Registers:\n"); debugf2(" Branch 0:\n"); - for (slot_row = 0; slot_row < NUM_MTRS_PER_BRANCH; slot_row++) + for (slot_row = 0; slot_row < DIMMS_PER_CHANNEL; slot_row++) decode_mtr(slot_row, pvt->b0_mtr[slot_row]); pci_read_config_word(pvt->branch_0, AMBPRESENT_0, @@ -1122,7 +1141,7 @@ static void i5400_get_mc_regs(struct mem_ctl_info *mci) } else { /* Read and dump branch 1's MTRs */ debugf2(" Branch 1:\n"); - for (slot_row = 0; slot_row < NUM_MTRS_PER_BRANCH; slot_row++) + for (slot_row = 0; slot_row < DIMMS_PER_CHANNEL; slot_row++) decode_mtr(slot_row, pvt->b1_mtr[slot_row]); pci_read_config_word(pvt->branch_1, AMBPRESENT_0, @@ -1141,7 +1160,7 @@ static void i5400_get_mc_regs(struct mem_ctl_info *mci) } /* - * i5400_init_csrows Initialize the 'csrows' table within + * i5400_init_dimms Initialize the 'dimms' table within * the mci control structure with the * addressing of memory. * @@ -1149,64 +1168,68 @@ static void i5400_get_mc_regs(struct mem_ctl_info *mci) * 0 success * 1 no actual memory found on this MC */ -static int i5400_init_csrows(struct mem_ctl_info *mci) +static int i5400_init_dimms(struct mem_ctl_info *mci) { struct i5400_pvt *pvt; - struct csrow_info *p_csrow; - int empty, channel_count; - int max_csrows; + struct dimm_info *dimm; + int ndimms, channel_count; + int max_dimms; int mtr; - int csrow_megs; - int channel; - int csrow; + int size_mb; + int channel, slot; pvt = mci->pvt_info; channel_count = pvt->maxch; - max_csrows = pvt->maxdimmperch; + max_dimms = pvt->maxdimmperch; - empty = 1; /* Assume NO memory */ + ndimms = 0; - for (csrow = 0; csrow < max_csrows; csrow++) { - p_csrow = &mci->csrows[csrow]; - - p_csrow->csrow_idx = csrow; - - /* use branch 0 for the basis */ - mtr = determine_mtr(pvt, csrow, 0); - - /* if no DIMMS on this row, continue */ - if (!MTR_DIMMS_PRESENT(mtr)) - continue; - - /* FAKE OUT VALUES, FIXME */ - p_csrow->first_page = 0 + csrow * 20; - p_csrow->last_page = 9 + csrow * 20; - p_csrow->page_mask = 0xFFF; - - p_csrow->grain = 8; - - csrow_megs = 0; - for (channel = 0; channel < pvt->maxch; channel++) - csrow_megs += pvt->dimm_info[csrow][channel].megabytes; - - p_csrow->nr_pages = csrow_megs << 8; - - /* Assume DDR2 for now */ - p_csrow->mtype = MEM_FB_DDR2; - - /* ask what device type on this row */ - if (MTR_DRAM_WIDTH(mtr)) - p_csrow->dtype = DEV_X8; - else - p_csrow->dtype = DEV_X4; - - p_csrow->edac_mode = EDAC_S8ECD8ED; - - empty = 0; + /* + * FIXME: remove pvt->dimm_info[slot][channel] and use the 3 + * layers here. + */ + for (channel = 0; channel < mci->layers[0].size * mci->layers[1].size; + channel++) { + for (slot = 0; slot < mci->layers[2].size; slot++) { + mtr = determine_mtr(pvt, slot, channel); + + /* if no DIMMS on this slot, continue */ + if (!MTR_DIMMS_PRESENT(mtr)) + continue; + + dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, + channel / 2, channel % 2, slot); + + size_mb = pvt->dimm_info[slot][channel].megabytes; + + debugf2("%s: dimm%zd (branch %d channel %d slot %d): %d.%03d GB\n", + __func__, dimm - mci->dimms, + channel / 2, channel % 2, slot, + size_mb / 1000, size_mb % 1000); + + dimm->nr_pages = size_mb << 8; + dimm->grain = 8; + dimm->dtype = MTR_DRAM_WIDTH(mtr) ? DEV_X8 : DEV_X4; + dimm->mtype = MEM_FB_DDR2; + /* + * The eccc mechanism is SDDC (aka SECC), with + * is similar to Chipkill. + */ + dimm->edac_mode = MTR_DRAM_WIDTH(mtr) ? + EDAC_S8ECD8ED : EDAC_S4ECD4ED; + ndimms++; + } } - return empty; + /* + * When just one memory is provided, it should be at location (0,0,0). + * With such single-DIMM mode, the SDCC algorithm degrades to SECDEC+. + */ + if (ndimms == 1) + mci->dimms[0].edac_mode = EDAC_SECDED; + + return (ndimms == 0); } /* @@ -1242,9 +1265,7 @@ static int i5400_probe1(struct pci_dev *pdev, int dev_idx) { struct mem_ctl_info *mci; struct i5400_pvt *pvt; - int num_channels; - int num_dimms_per_channel; - int num_csrows; + struct edac_mc_layer layers[3]; if (dev_idx >= ARRAY_SIZE(i5400_devs)) return -EINVAL; @@ -1258,23 +1279,21 @@ static int i5400_probe1(struct pci_dev *pdev, int dev_idx) if (PCI_FUNC(pdev->devfn) != 0) return -ENODEV; - /* As we don't have a motherboard identification routine to determine - * actual number of slots/dimms per channel, we thus utilize the - * resource as specified by the chipset. Thus, we might have - * have more DIMMs per channel than actually on the mobo, but this - * allows the driver to support up to the chipset max, without - * some fancy mobo determination. + /* + * allocate a new MC control structure + * + * This drivers uses the DIMM slot as "csrow" and the rest as "channel". */ - num_dimms_per_channel = MAX_DIMMS_PER_CHANNEL; - num_channels = MAX_CHANNELS; - num_csrows = num_dimms_per_channel; - - debugf0("MC: %s(): Number of - Channels= %d DIMMS= %d CSROWS= %d\n", - __func__, num_channels, num_dimms_per_channel, num_csrows); - - /* allocate a new MC control structure */ - mci = edac_mc_alloc(sizeof(*pvt), num_csrows, num_channels, 0); - + layers[0].type = EDAC_MC_LAYER_BRANCH; + layers[0].size = MAX_BRANCHES; + layers[0].is_virt_csrow = false; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = CHANNELS_PER_BRANCH; + layers[1].is_virt_csrow = false; + layers[2].type = EDAC_MC_LAYER_SLOT; + layers[2].size = DIMMS_PER_CHANNEL; + layers[2].is_virt_csrow = true; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (mci == NULL) return -ENOMEM; @@ -1284,8 +1303,8 @@ static int i5400_probe1(struct pci_dev *pdev, int dev_idx) pvt = mci->pvt_info; pvt->system_address = pdev; /* Record this device in our private */ - pvt->maxch = num_channels; - pvt->maxdimmperch = num_dimms_per_channel; + pvt->maxch = MAX_CHANNELS; + pvt->maxdimmperch = DIMMS_PER_CHANNEL; /* 'get' the pci devices we want to reserve for our use */ if (i5400_get_devices(mci, dev_idx)) @@ -1307,13 +1326,13 @@ static int i5400_probe1(struct pci_dev *pdev, int dev_idx) /* Set the function pointer to an actual operation function */ mci->edac_check = i5400_check_error; - /* initialize the MC control structure 'csrows' table + /* initialize the MC control structure 'dimms' table * with the mapping and control information */ - if (i5400_init_csrows(mci)) { + if (i5400_init_dimms(mci)) { debugf0("MC: Setting mci->edac_cap to EDAC_FLAG_NONE\n" - " because i5400_init_csrows() returned nonzero " + " because i5400_init_dimms() returned nonzero " "value\n"); - mci->edac_cap = EDAC_FLAG_NONE; /* no csrows found */ + mci->edac_cap = EDAC_FLAG_NONE; /* no dimms found */ } else { debugf1("MC: Enable error reporting now\n"); i5400_enable_error_reporting(mci); diff --git a/drivers/edac/i7300_edac.c b/drivers/edac/i7300_edac.c index 3bafa3bca14..97c22fd650e 100644 --- a/drivers/edac/i7300_edac.c +++ b/drivers/edac/i7300_edac.c @@ -464,17 +464,14 @@ static void i7300_process_fbd_error(struct mem_ctl_info *mci) FERR_FAT_FBD, error_reg); snprintf(pvt->tmp_prt_buffer, PAGE_SIZE, - "FATAL (Branch=%d DRAM-Bank=%d %s " - "RAS=%d CAS=%d Err=0x%lx (%s))", - branch, bank, - is_wr ? "RDWR" : "RD", - ras, cas, - errors, specific); - - /* Call the helper to output message */ - edac_mc_handle_fbd_ue(mci, rank, branch << 1, - (branch << 1) + 1, - pvt->tmp_prt_buffer); + "Bank=%d RAS=%d CAS=%d Err=0x%lx (%s))", + bank, ras, cas, errors, specific); + + edac_mc_handle_error(HW_EVENT_ERR_FATAL, mci, 0, 0, 0, + branch, -1, rank, + is_wr ? "Write error" : "Read error", + pvt->tmp_prt_buffer, NULL); + } /* read in the 1st NON-FATAL error register */ @@ -513,23 +510,14 @@ static void i7300_process_fbd_error(struct mem_ctl_info *mci) /* Form out message */ snprintf(pvt->tmp_prt_buffer, PAGE_SIZE, - "Corrected error (Branch=%d, Channel %d), " - " DRAM-Bank=%d %s " - "RAS=%d CAS=%d, CE Err=0x%lx, Syndrome=0x%08x(%s))", - branch, channel, - bank, - is_wr ? "RDWR" : "RD", - ras, cas, - errors, syndrome, specific); - - /* - * Call the helper to output message - * NOTE: Errors are reported per-branch, and not per-channel - * Currently, we don't know how to identify the right - * channel. - */ - edac_mc_handle_fbd_ce(mci, rank, channel, - pvt->tmp_prt_buffer); + "DRAM-Bank=%d RAS=%d CAS=%d, Err=0x%lx (%s))", + bank, ras, cas, errors, specific); + + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 0, 0, + syndrome, + branch >> 1, channel % 2, rank, + is_wr ? "Write error" : "Read error", + pvt->tmp_prt_buffer, NULL); } return; } @@ -617,8 +605,7 @@ static void i7300_enable_error_reporting(struct mem_ctl_info *mci) static int decode_mtr(struct i7300_pvt *pvt, int slot, int ch, int branch, struct i7300_dimm_info *dinfo, - struct csrow_info *p_csrow, - u32 *nr_pages) + struct dimm_info *dimm) { int mtr, ans, addrBits, channel; @@ -650,7 +637,6 @@ static int decode_mtr(struct i7300_pvt *pvt, addrBits -= 3; /* 8 bits per bytes */ dinfo->megabytes = 1 << addrBits; - *nr_pages = dinfo->megabytes << 8; debugf2("\t\tWIDTH: x%d\n", MTR_DRAM_WIDTH(mtr)); @@ -663,11 +649,6 @@ static int decode_mtr(struct i7300_pvt *pvt, debugf2("\t\tNUMCOL: %s\n", numcol_toString[MTR_DIMM_COLS(mtr)]); debugf2("\t\tSIZE: %d MB\n", dinfo->megabytes); - p_csrow->grain = 8; - p_csrow->mtype = MEM_FB_DDR2; - p_csrow->csrow_idx = slot; - p_csrow->page_mask = 0; - /* * The type of error detection actually depends of the * mode of operation. When it is just one single memory chip, at @@ -677,15 +658,18 @@ static int decode_mtr(struct i7300_pvt *pvt, * See datasheet Sections 7.3.6 to 7.3.8 */ + dimm->nr_pages = MiB_TO_PAGES(dinfo->megabytes); + dimm->grain = 8; + dimm->mtype = MEM_FB_DDR2; if (IS_SINGLE_MODE(pvt->mc_settings_a)) { - p_csrow->edac_mode = EDAC_SECDED; + dimm->edac_mode = EDAC_SECDED; debugf2("\t\tECC code is 8-byte-over-32-byte SECDED+ code\n"); } else { debugf2("\t\tECC code is on Lockstep mode\n"); if (MTR_DRAM_WIDTH(mtr) == 8) - p_csrow->edac_mode = EDAC_S8ECD8ED; + dimm->edac_mode = EDAC_S8ECD8ED; else - p_csrow->edac_mode = EDAC_S4ECD4ED; + dimm->edac_mode = EDAC_S4ECD4ED; } /* ask what device type on this row */ @@ -694,9 +678,9 @@ static int decode_mtr(struct i7300_pvt *pvt, IS_SCRBALGO_ENHANCED(pvt->mc_settings) ? "enhanced" : "normal"); - p_csrow->dtype = DEV_X8; + dimm->dtype = DEV_X8; } else - p_csrow->dtype = DEV_X4; + dimm->dtype = DEV_X4; return mtr; } @@ -774,11 +758,10 @@ static int i7300_init_csrows(struct mem_ctl_info *mci) { struct i7300_pvt *pvt; struct i7300_dimm_info *dinfo; - struct csrow_info *p_csrow; int rc = -ENODEV; int mtr; int ch, branch, slot, channel; - u32 last_page = 0, nr_pages; + struct dimm_info *dimm; pvt = mci->pvt_info; @@ -809,25 +792,23 @@ static int i7300_init_csrows(struct mem_ctl_info *mci) pci_read_config_word(pvt->pci_dev_2x_0_fbd_branch[branch], where, &pvt->mtr[slot][branch]); - for (ch = 0; ch < MAX_BRANCHES; ch++) { + for (ch = 0; ch < MAX_CH_PER_BRANCH; ch++) { int channel = to_channel(ch, branch); + dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, + mci->n_layers, branch, ch, slot); + dinfo = &pvt->dimm_info[slot][channel]; - p_csrow = &mci->csrows[slot]; mtr = decode_mtr(pvt, slot, ch, branch, - dinfo, p_csrow, &nr_pages); + dinfo, dimm); + /* if no DIMMS on this row, continue */ if (!MTR_DIMMS_PRESENT(mtr)) continue; - /* Update per_csrow memory count */ - p_csrow->nr_pages += nr_pages; - p_csrow->first_page = last_page; - last_page += nr_pages; - p_csrow->last_page = last_page; - rc = 0; + } } } @@ -1042,10 +1023,8 @@ static int __devinit i7300_init_one(struct pci_dev *pdev, const struct pci_device_id *id) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[3]; struct i7300_pvt *pvt; - int num_channels; - int num_dimms_per_channel; - int num_csrows; int rc; /* wake up device */ @@ -1062,23 +1041,17 @@ static int __devinit i7300_init_one(struct pci_dev *pdev, if (PCI_FUNC(pdev->devfn) != 0) return -ENODEV; - /* As we don't have a motherboard identification routine to determine - * actual number of slots/dimms per channel, we thus utilize the - * resource as specified by the chipset. Thus, we might have - * have more DIMMs per channel than actually on the mobo, but this - * allows the driver to support up to the chipset max, without - * some fancy mobo determination. - */ - num_dimms_per_channel = MAX_SLOTS; - num_channels = MAX_CHANNELS; - num_csrows = MAX_SLOTS * MAX_CHANNELS; - - debugf0("MC: %s(): Number of - Channels= %d DIMMS= %d CSROWS= %d\n", - __func__, num_channels, num_dimms_per_channel, num_csrows); - /* allocate a new MC control structure */ - mci = edac_mc_alloc(sizeof(*pvt), num_csrows, num_channels, 0); - + layers[0].type = EDAC_MC_LAYER_BRANCH; + layers[0].size = MAX_BRANCHES; + layers[0].is_virt_csrow = false; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = MAX_CH_PER_BRANCH; + layers[1].is_virt_csrow = true; + layers[2].type = EDAC_MC_LAYER_SLOT; + layers[2].size = MAX_SLOTS; + layers[2].is_virt_csrow = true; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (mci == NULL) return -ENOMEM; diff --git a/drivers/edac/i7core_edac.c b/drivers/edac/i7core_edac.c index 7f1dfcc4e59..d27778f65a5 100644 --- a/drivers/edac/i7core_edac.c +++ b/drivers/edac/i7core_edac.c @@ -221,7 +221,9 @@ struct i7core_inject { }; struct i7core_channel { - u32 ranks; + bool is_3dimms_present; + bool is_single_4rank; + bool has_4rank; u32 dimms; }; @@ -257,7 +259,6 @@ struct i7core_pvt { struct i7core_channel channel[NUM_CHANS]; int ce_count_available; - int csrow_map[NUM_CHANS][MAX_DIMMS]; /* ECC corrected errors counts per udimm */ unsigned long udimm_ce_count[MAX_DIMMS]; @@ -492,116 +493,15 @@ static void free_i7core_dev(struct i7core_dev *i7core_dev) /**************************************************************************** Memory check routines ****************************************************************************/ -static struct pci_dev *get_pdev_slot_func(u8 socket, unsigned slot, - unsigned func) -{ - struct i7core_dev *i7core_dev = get_i7core_dev(socket); - int i; - - if (!i7core_dev) - return NULL; - - for (i = 0; i < i7core_dev->n_devs; i++) { - if (!i7core_dev->pdev[i]) - continue; - - if (PCI_SLOT(i7core_dev->pdev[i]->devfn) == slot && - PCI_FUNC(i7core_dev->pdev[i]->devfn) == func) { - return i7core_dev->pdev[i]; - } - } - - return NULL; -} - -/** - * i7core_get_active_channels() - gets the number of channels and csrows - * @socket: Quick Path Interconnect socket - * @channels: Number of channels that will be returned - * @csrows: Number of csrows found - * - * Since EDAC core needs to know in advance the number of available channels - * and csrows, in order to allocate memory for csrows/channels, it is needed - * to run two similar steps. At the first step, implemented on this function, - * it checks the number of csrows/channels present at one socket. - * this is used in order to properly allocate the size of mci components. - * - * It should be noticed that none of the current available datasheets explain - * or even mention how csrows are seen by the memory controller. So, we need - * to add a fake description for csrows. - * So, this driver is attributing one DIMM memory for one csrow. - */ -static int i7core_get_active_channels(const u8 socket, unsigned *channels, - unsigned *csrows) -{ - struct pci_dev *pdev = NULL; - int i, j; - u32 status, control; - - *channels = 0; - *csrows = 0; - - pdev = get_pdev_slot_func(socket, 3, 0); - if (!pdev) { - i7core_printk(KERN_ERR, "Couldn't find socket %d fn 3.0!!!\n", - socket); - return -ENODEV; - } - - /* Device 3 function 0 reads */ - pci_read_config_dword(pdev, MC_STATUS, &status); - pci_read_config_dword(pdev, MC_CONTROL, &control); - - for (i = 0; i < NUM_CHANS; i++) { - u32 dimm_dod[3]; - /* Check if the channel is active */ - if (!(control & (1 << (8 + i)))) - continue; - - /* Check if the channel is disabled */ - if (status & (1 << i)) - continue; - - pdev = get_pdev_slot_func(socket, i + 4, 1); - if (!pdev) { - i7core_printk(KERN_ERR, "Couldn't find socket %d " - "fn %d.%d!!!\n", - socket, i + 4, 1); - return -ENODEV; - } - /* Devices 4-6 function 1 */ - pci_read_config_dword(pdev, - MC_DOD_CH_DIMM0, &dimm_dod[0]); - pci_read_config_dword(pdev, - MC_DOD_CH_DIMM1, &dimm_dod[1]); - pci_read_config_dword(pdev, - MC_DOD_CH_DIMM2, &dimm_dod[2]); - (*channels)++; - - for (j = 0; j < 3; j++) { - if (!DIMM_PRESENT(dimm_dod[j])) - continue; - (*csrows)++; - } - } - - debugf0("Number of active channels on socket %d: %d\n", - socket, *channels); - - return 0; -} - -static int get_dimm_config(const struct mem_ctl_info *mci) +static int get_dimm_config(struct mem_ctl_info *mci) { struct i7core_pvt *pvt = mci->pvt_info; - struct csrow_info *csr; struct pci_dev *pdev; int i, j; - int csrow = 0; - unsigned long last_page = 0; enum edac_type mode; enum mem_type mtype; + struct dimm_info *dimm; /* Get data from the MC register, function 0 */ pdev = pvt->pci_mcr[0]; @@ -657,21 +557,20 @@ static int get_dimm_config(const struct mem_ctl_info *mci) pci_read_config_dword(pvt->pci_ch[i][0], MC_CHANNEL_DIMM_INIT_PARAMS, &data); - pvt->channel[i].ranks = (data & QUAD_RANK_PRESENT) ? - 4 : 2; + + if (data & THREE_DIMMS_PRESENT) + pvt->channel[i].is_3dimms_present = true; + + if (data & SINGLE_QUAD_RANK_PRESENT) + pvt->channel[i].is_single_4rank = true; + + if (data & QUAD_RANK_PRESENT) + pvt->channel[i].has_4rank = true; if (data & REGISTERED_DIMM) mtype = MEM_RDDR3; else mtype = MEM_DDR3; -#if 0 - if (data & THREE_DIMMS_PRESENT) - pvt->channel[i].dimms = 3; - else if (data & SINGLE_QUAD_RANK_PRESENT) - pvt->channel[i].dimms = 1; - else - pvt->channel[i].dimms = 2; -#endif /* Devices 4-6 function 1 */ pci_read_config_dword(pvt->pci_ch[i][1], @@ -682,11 +581,13 @@ static int get_dimm_config(const struct mem_ctl_info *mci) MC_DOD_CH_DIMM2, &dimm_dod[2]); debugf0("Ch%d phy rd%d, wr%d (0x%08x): " - "%d ranks, %cDIMMs\n", + "%s%s%s%cDIMMs\n", i, RDLCH(pvt->info.ch_map, i), WRLCH(pvt->info.ch_map, i), data, - pvt->channel[i].ranks, + pvt->channel[i].is_3dimms_present ? "3DIMMS " : "", + pvt->channel[i].is_3dimms_present ? "SINGLE_4R " : "", + pvt->channel[i].has_4rank ? "HAS_4R " : "", (data & REGISTERED_DIMM) ? 'R' : 'U'); for (j = 0; j < 3; j++) { @@ -696,6 +597,8 @@ static int get_dimm_config(const struct mem_ctl_info *mci) if (!DIMM_PRESENT(dimm_dod[j])) continue; + dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, + i, j, 0); banks = numbank(MC_DOD_NUMBANK(dimm_dod[j])); ranks = numrank(MC_DOD_NUMRANK(dimm_dod[j])); rows = numrow(MC_DOD_NUMROW(dimm_dod[j])); @@ -704,8 +607,6 @@ static int get_dimm_config(const struct mem_ctl_info *mci) /* DDR3 has 8 I/O banks */ size = (rows * cols * banks * ranks) >> (20 - 3); - pvt->channel[i].dimms++; - debugf0("\tdimm %d %d Mb offset: %x, " "bank: %d, rank: %d, row: %#x, col: %#x\n", j, size, @@ -714,44 +615,28 @@ static int get_dimm_config(const struct mem_ctl_info *mci) npages = MiB_TO_PAGES(size); - csr = &mci->csrows[csrow]; - csr->first_page = last_page + 1; - last_page += npages; - csr->last_page = last_page; - csr->nr_pages = npages; - - csr->page_mask = 0; - csr->grain = 8; - csr->csrow_idx = csrow; - csr->nr_channels = 1; - - csr->channels[0].chan_idx = i; - csr->channels[0].ce_count = 0; - - pvt->csrow_map[i][j] = csrow; + dimm->nr_pages = npages; switch (banks) { case 4: - csr->dtype = DEV_X4; + dimm->dtype = DEV_X4; break; case 8: - csr->dtype = DEV_X8; + dimm->dtype = DEV_X8; break; case 16: - csr->dtype = DEV_X16; + dimm->dtype = DEV_X16; break; default: - csr->dtype = DEV_UNKNOWN; + dimm->dtype = DEV_UNKNOWN; } - csr->edac_mode = mode; - csr->mtype = mtype; - snprintf(csr->channels[0].label, - sizeof(csr->channels[0].label), - "CPU#%uChannel#%u_DIMM#%u", - pvt->i7core_dev->socket, i, j); - - csrow++; + snprintf(dimm->label, sizeof(dimm->label), + "CPU#%uChannel#%u_DIMM#%u", + pvt->i7core_dev->socket, i, j); + dimm->grain = 8; + dimm->edac_mode = mode; + dimm->mtype = mtype; } pci_read_config_dword(pdev, MC_SAG_CH_0, &value[0]); @@ -1567,22 +1452,16 @@ error: /**************************************************************************** Error check routines ****************************************************************************/ -static void i7core_rdimm_update_csrow(struct mem_ctl_info *mci, +static void i7core_rdimm_update_errcount(struct mem_ctl_info *mci, const int chan, const int dimm, const int add) { - char *msg; - struct i7core_pvt *pvt = mci->pvt_info; - int row = pvt->csrow_map[chan][dimm], i; + int i; for (i = 0; i < add; i++) { - msg = kasprintf(GFP_KERNEL, "Corrected error " - "(Socket=%d channel=%d dimm=%d)", - pvt->i7core_dev->socket, chan, dimm); - - edac_mc_handle_fbd_ce(mci, row, 0, msg); - kfree (msg); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 0, 0, 0, + chan, dimm, -1, "error", "", NULL); } } @@ -1623,11 +1502,11 @@ static void i7core_rdimm_update_ce_count(struct mem_ctl_info *mci, /*updated the edac core */ if (add0 != 0) - i7core_rdimm_update_csrow(mci, chan, 0, add0); + i7core_rdimm_update_errcount(mci, chan, 0, add0); if (add1 != 0) - i7core_rdimm_update_csrow(mci, chan, 1, add1); + i7core_rdimm_update_errcount(mci, chan, 1, add1); if (add2 != 0) - i7core_rdimm_update_csrow(mci, chan, 2, add2); + i7core_rdimm_update_errcount(mci, chan, 2, add2); } @@ -1747,20 +1626,30 @@ static void i7core_mce_output_error(struct mem_ctl_info *mci, const struct mce *m) { struct i7core_pvt *pvt = mci->pvt_info; - char *type, *optype, *err, *msg; + char *type, *optype, *err, msg[80]; + enum hw_event_mc_err_type tp_event; unsigned long error = m->status & 0x1ff0000l; + bool uncorrected_error = m->mcgstatus & 1ll << 61; + bool ripv = m->mcgstatus & 1; u32 optypenum = (m->status >> 4) & 0x07; u32 core_err_cnt = (m->status >> 38) & 0x7fff; u32 dimm = (m->misc >> 16) & 0x3; u32 channel = (m->misc >> 18) & 0x3; u32 syndrome = m->misc >> 32; u32 errnum = find_first_bit(&error, 32); - int csrow; - if (m->mcgstatus & 1) - type = "FATAL"; - else - type = "NON_FATAL"; + if (uncorrected_error) { + if (ripv) { + type = "FATAL"; + tp_event = HW_EVENT_ERR_FATAL; + } else { + type = "NON_FATAL"; + tp_event = HW_EVENT_ERR_UNCORRECTED; + } + } else { + type = "CORRECTED"; + tp_event = HW_EVENT_ERR_CORRECTED; + } switch (optypenum) { case 0: @@ -1815,27 +1704,20 @@ static void i7core_mce_output_error(struct mem_ctl_info *mci, err = "unknown"; } - /* FIXME: should convert addr into bank and rank information */ - msg = kasprintf(GFP_ATOMIC, - "%s (addr = 0x%08llx, cpu=%d, Dimm=%d, Channel=%d, " - "syndrome=0x%08x, count=%d, Err=%08llx:%08llx (%s: %s))\n", - type, (long long) m->addr, m->cpu, dimm, channel, - syndrome, core_err_cnt, (long long)m->status, - (long long)m->misc, optype, err); - - debugf0("%s", msg); - - csrow = pvt->csrow_map[channel][dimm]; + snprintf(msg, sizeof(msg), "count=%d %s", core_err_cnt, optype); - /* Call the helper to output message */ - if (m->mcgstatus & 1) - edac_mc_handle_fbd_ue(mci, csrow, 0, - 0 /* FIXME: should be channel here */, msg); - else if (!pvt->is_registered) - edac_mc_handle_fbd_ce(mci, csrow, - 0 /* FIXME: should be channel here */, msg); - - kfree(msg); + /* + * Call the helper to output message + * FIXME: what to do if core_err_cnt > 1? Currently, it generates + * only one event + */ + if (uncorrected_error || !pvt->is_registered) + edac_mc_handle_error(tp_event, mci, + m->addr >> PAGE_SHIFT, + m->addr & ~PAGE_MASK, + syndrome, + channel, dimm, -1, + err, msg, m); } /* @@ -2252,15 +2134,19 @@ static int i7core_register_mci(struct i7core_dev *i7core_dev) { struct mem_ctl_info *mci; struct i7core_pvt *pvt; - int rc, channels, csrows; - - /* Check the number of active and not disabled channels */ - rc = i7core_get_active_channels(i7core_dev->socket, &channels, &csrows); - if (unlikely(rc < 0)) - return rc; + int rc; + struct edac_mc_layer layers[2]; /* allocate a new MC control structure */ - mci = edac_mc_alloc(sizeof(*pvt), csrows, channels, i7core_dev->socket); + + layers[0].type = EDAC_MC_LAYER_CHANNEL; + layers[0].size = NUM_CHANS; + layers[0].is_virt_csrow = false; + layers[1].type = EDAC_MC_LAYER_SLOT; + layers[1].size = MAX_DIMMS; + layers[1].is_virt_csrow = true; + mci = edac_mc_alloc(i7core_dev->socket, ARRAY_SIZE(layers), layers, + sizeof(*pvt)); if (unlikely(!mci)) return -ENOMEM; diff --git a/drivers/edac/i82443bxgx_edac.c b/drivers/edac/i82443bxgx_edac.c index 3bf2b2f490e..52072c28a8a 100644 --- a/drivers/edac/i82443bxgx_edac.c +++ b/drivers/edac/i82443bxgx_edac.c @@ -12,7 +12,7 @@ * 440GX fix by Jason Uhlenkott <juhlenko@akamai.com>. * * Written with reference to 82443BX Host Bridge Datasheet: - * http://download.intel.com/design/chipsets/datashts/29063301.pdf + * http://download.intel.com/design/chipsets/datashts/29063301.pdf * references to this document given in []. * * This module doesn't support the 440LX, but it may be possible to @@ -156,19 +156,19 @@ static int i82443bxgx_edacmc_process_error_info(struct mem_ctl_info *mci, if (info->eap & I82443BXGX_EAP_OFFSET_SBE) { error_found = 1; if (handle_errors) - edac_mc_handle_ce(mci, page, pageoffset, - /* 440BX/GX don't make syndrome information - * available */ - 0, edac_mc_find_csrow_by_page(mci, page), 0, - mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, pageoffset, 0, + edac_mc_find_csrow_by_page(mci, page), + 0, -1, mci->ctl_name, "", NULL); } if (info->eap & I82443BXGX_EAP_OFFSET_MBE) { error_found = 1; if (handle_errors) - edac_mc_handle_ue(mci, page, pageoffset, - edac_mc_find_csrow_by_page(mci, page), - mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, pageoffset, 0, + edac_mc_find_csrow_by_page(mci, page), + 0, -1, mci->ctl_name, "", NULL); } return error_found; @@ -189,6 +189,7 @@ static void i82443bxgx_init_csrows(struct mem_ctl_info *mci, enum mem_type mtype) { struct csrow_info *csrow; + struct dimm_info *dimm; int index; u8 drbar, dramc; u32 row_base, row_high_limit, row_high_limit_last; @@ -197,6 +198,8 @@ static void i82443bxgx_init_csrows(struct mem_ctl_info *mci, row_high_limit_last = 0; for (index = 0; index < mci->nr_csrows; index++) { csrow = &mci->csrows[index]; + dimm = csrow->channels[0].dimm; + pci_read_config_byte(pdev, I82443BXGX_DRB + index, &drbar); debugf1("MC%d: %s: %s() Row=%d DRB = %#0x\n", mci->mc_idx, __FILE__, __func__, index, drbar); @@ -217,14 +220,14 @@ static void i82443bxgx_init_csrows(struct mem_ctl_info *mci, row_base = row_high_limit_last; csrow->first_page = row_base >> PAGE_SHIFT; csrow->last_page = (row_high_limit >> PAGE_SHIFT) - 1; - csrow->nr_pages = csrow->last_page - csrow->first_page + 1; + dimm->nr_pages = csrow->last_page - csrow->first_page + 1; /* EAP reports in 4kilobyte granularity [61] */ - csrow->grain = 1 << 12; - csrow->mtype = mtype; + dimm->grain = 1 << 12; + dimm->mtype = mtype; /* I don't think 440BX can tell you device type? FIXME? */ - csrow->dtype = DEV_UNKNOWN; + dimm->dtype = DEV_UNKNOWN; /* Mode is global to all rows on 440BX */ - csrow->edac_mode = edac_mode; + dimm->edac_mode = edac_mode; row_high_limit_last = row_high_limit; } } @@ -232,6 +235,7 @@ static void i82443bxgx_init_csrows(struct mem_ctl_info *mci, static int i82443bxgx_edacmc_probe1(struct pci_dev *pdev, int dev_idx) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; u8 dramc; u32 nbxcfg, ecc_mode; enum mem_type mtype; @@ -245,8 +249,13 @@ static int i82443bxgx_edacmc_probe1(struct pci_dev *pdev, int dev_idx) if (pci_read_config_dword(pdev, I82443BXGX_NBXCFG, &nbxcfg)) return -EIO; - mci = edac_mc_alloc(0, I82443BXGX_NR_CSROWS, I82443BXGX_NR_CHANS, 0); - + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = I82443BXGX_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = I82443BXGX_NR_CHANS; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); if (mci == NULL) return -ENOMEM; diff --git a/drivers/edac/i82860_edac.c b/drivers/edac/i82860_edac.c index c779092d18d..08045059d10 100644 --- a/drivers/edac/i82860_edac.c +++ b/drivers/edac/i82860_edac.c @@ -99,6 +99,7 @@ static int i82860_process_error_info(struct mem_ctl_info *mci, struct i82860_error_info *info, int handle_errors) { + struct dimm_info *dimm; int row; if (!(info->errsts2 & 0x0003)) @@ -108,18 +109,25 @@ static int i82860_process_error_info(struct mem_ctl_info *mci, return 1; if ((info->errsts ^ info->errsts2) & 0x0003) { - edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, "UE overwrote CE", "", NULL); info->errsts = info->errsts2; } info->eap >>= PAGE_SHIFT; row = edac_mc_find_csrow_by_page(mci, info->eap); + dimm = mci->csrows[row].channels[0].dimm; if (info->errsts & 0x0002) - edac_mc_handle_ue(mci, info->eap, 0, row, "i82860 UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + info->eap, 0, 0, + dimm->location[0], dimm->location[1], -1, + "i82860 UE", "", NULL); else - edac_mc_handle_ce(mci, info->eap, 0, info->derrsyn, row, 0, - "i82860 UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + info->eap, 0, info->derrsyn, + dimm->location[0], dimm->location[1], -1, + "i82860 CE", "", NULL); return 1; } @@ -140,6 +148,7 @@ static void i82860_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev) u16 value; u32 cumul_size; struct csrow_info *csrow; + struct dimm_info *dimm; int index; pci_read_config_word(pdev, I82860_MCHCFG, &mchcfg_ddim); @@ -153,6 +162,8 @@ static void i82860_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev) */ for (index = 0; index < mci->nr_csrows; index++) { csrow = &mci->csrows[index]; + dimm = csrow->channels[0].dimm; + pci_read_config_word(pdev, I82860_GBA + index * 2, &value); cumul_size = (value & I82860_GBA_MASK) << (I82860_GBA_SHIFT - PAGE_SHIFT); @@ -164,30 +175,38 @@ static void i82860_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev) csrow->first_page = last_cumul_size; csrow->last_page = cumul_size - 1; - csrow->nr_pages = cumul_size - last_cumul_size; + dimm->nr_pages = cumul_size - last_cumul_size; last_cumul_size = cumul_size; - csrow->grain = 1 << 12; /* I82860_EAP has 4KiB reolution */ - csrow->mtype = MEM_RMBS; - csrow->dtype = DEV_UNKNOWN; - csrow->edac_mode = mchcfg_ddim ? EDAC_SECDED : EDAC_NONE; + dimm->grain = 1 << 12; /* I82860_EAP has 4KiB reolution */ + dimm->mtype = MEM_RMBS; + dimm->dtype = DEV_UNKNOWN; + dimm->edac_mode = mchcfg_ddim ? EDAC_SECDED : EDAC_NONE; } } static int i82860_probe1(struct pci_dev *pdev, int dev_idx) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct i82860_error_info discard; - /* RDRAM has channels but these don't map onto the abstractions that - edac uses. - The device groups from the GRA registers seem to map reasonably - well onto the notion of a chip select row. - There are 16 GRA registers and since the name is associated with - the channel and the GRA registers map to physical devices so we are - going to make 1 channel for group. + /* + * RDRAM has channels but these don't map onto the csrow abstraction. + * According with the datasheet, there are 2 Rambus channels, supporting + * up to 16 direct RDRAM devices. + * The device groups from the GRA registers seem to map reasonably + * well onto the notion of a chip select row. + * There are 16 GRA registers and since the name is associated with + * the channel and the GRA registers map to physical devices so we are + * going to make 1 channel for group. */ - mci = edac_mc_alloc(0, 16, 1, 0); - + layers[0].type = EDAC_MC_LAYER_CHANNEL; + layers[0].size = 2; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_SLOT; + layers[1].size = 8; + layers[1].is_virt_csrow = true; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); if (!mci) return -ENOMEM; diff --git a/drivers/edac/i82875p_edac.c b/drivers/edac/i82875p_edac.c index 10f15d85fb5..b613e31c16e 100644 --- a/drivers/edac/i82875p_edac.c +++ b/drivers/edac/i82875p_edac.c @@ -38,7 +38,8 @@ #endif /* PCI_DEVICE_ID_INTEL_82875_6 */ /* four csrows in dual channel, eight in single channel */ -#define I82875P_NR_CSROWS(nr_chans) (8/(nr_chans)) +#define I82875P_NR_DIMMS 8 +#define I82875P_NR_CSROWS(nr_chans) (I82875P_NR_DIMMS / (nr_chans)) /* Intel 82875p register addresses - device 0 function 0 - DRAM Controller */ #define I82875P_EAP 0x58 /* Error Address Pointer (32b) @@ -235,7 +236,9 @@ static int i82875p_process_error_info(struct mem_ctl_info *mci, return 1; if ((info->errsts ^ info->errsts2) & 0x0081) { - edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, + "UE overwrote CE", "", NULL); info->errsts = info->errsts2; } @@ -243,11 +246,15 @@ static int i82875p_process_error_info(struct mem_ctl_info *mci, row = edac_mc_find_csrow_by_page(mci, info->eap); if (info->errsts & 0x0080) - edac_mc_handle_ue(mci, info->eap, 0, row, "i82875p UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + info->eap, 0, 0, + row, -1, -1, + "i82875p UE", "", NULL); else - edac_mc_handle_ce(mci, info->eap, 0, info->derrsyn, row, - multi_chan ? (info->des & 0x1) : 0, - "i82875p CE"); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + info->eap, 0, info->derrsyn, + row, multi_chan ? (info->des & 0x1) : 0, + -1, "i82875p CE", "", NULL); return 1; } @@ -342,11 +349,13 @@ static void i82875p_init_csrows(struct mem_ctl_info *mci, void __iomem * ovrfl_window, u32 drc) { struct csrow_info *csrow; + struct dimm_info *dimm; + unsigned nr_chans = dual_channel_active(drc) + 1; unsigned long last_cumul_size; u8 value; u32 drc_ddim; /* DRAM Data Integrity Mode 0=none,2=edac */ - u32 cumul_size; - int index; + u32 cumul_size, nr_pages; + int index, j; drc_ddim = (drc >> 18) & 0x1; last_cumul_size = 0; @@ -369,12 +378,18 @@ static void i82875p_init_csrows(struct mem_ctl_info *mci, csrow->first_page = last_cumul_size; csrow->last_page = cumul_size - 1; - csrow->nr_pages = cumul_size - last_cumul_size; + nr_pages = cumul_size - last_cumul_size; last_cumul_size = cumul_size; - csrow->grain = 1 << 12; /* I82875P_EAP has 4KiB reolution */ - csrow->mtype = MEM_DDR; - csrow->dtype = DEV_UNKNOWN; - csrow->edac_mode = drc_ddim ? EDAC_SECDED : EDAC_NONE; + + for (j = 0; j < nr_chans; j++) { + dimm = csrow->channels[j].dimm; + + dimm->nr_pages = nr_pages / nr_chans; + dimm->grain = 1 << 12; /* I82875P_EAP has 4KiB reolution */ + dimm->mtype = MEM_DDR; + dimm->dtype = DEV_UNKNOWN; + dimm->edac_mode = drc_ddim ? EDAC_SECDED : EDAC_NONE; + } } } @@ -382,6 +397,7 @@ static int i82875p_probe1(struct pci_dev *pdev, int dev_idx) { int rc = -ENODEV; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct i82875p_pvt *pvt; struct pci_dev *ovrfl_pdev; void __iomem *ovrfl_window; @@ -397,9 +413,14 @@ static int i82875p_probe1(struct pci_dev *pdev, int dev_idx) return -ENODEV; drc = readl(ovrfl_window + I82875P_DRC); nr_chans = dual_channel_active(drc) + 1; - mci = edac_mc_alloc(sizeof(*pvt), I82875P_NR_CSROWS(nr_chans), - nr_chans, 0); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = I82875P_NR_CSROWS(nr_chans); + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = nr_chans; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (!mci) { rc = -ENOMEM; goto fail0; diff --git a/drivers/edac/i82975x_edac.c b/drivers/edac/i82975x_edac.c index 0cd8368f88f..433332c7cdb 100644 --- a/drivers/edac/i82975x_edac.c +++ b/drivers/edac/i82975x_edac.c @@ -29,7 +29,8 @@ #define PCI_DEVICE_ID_INTEL_82975_0 0x277c #endif /* PCI_DEVICE_ID_INTEL_82975_0 */ -#define I82975X_NR_CSROWS(nr_chans) (8/(nr_chans)) +#define I82975X_NR_DIMMS 8 +#define I82975X_NR_CSROWS(nr_chans) (I82975X_NR_DIMMS / (nr_chans)) /* Intel 82975X register addresses - device 0 function 0 - DRAM Controller */ #define I82975X_EAP 0x58 /* Dram Error Address Pointer (32b) @@ -287,7 +288,8 @@ static int i82975x_process_error_info(struct mem_ctl_info *mci, return 1; if ((info->errsts ^ info->errsts2) & 0x0003) { - edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, "UE overwrote CE", "", NULL); info->errsts = info->errsts2; } @@ -309,13 +311,18 @@ static int i82975x_process_error_info(struct mem_ctl_info *mci, chan = (mci->csrows[row].nr_channels == 1) ? 0 : info->eap & 1; offst = info->eap & ((1 << PAGE_SHIFT) - - (1 << mci->csrows[row].grain)); + (1 << mci->csrows[row].channels[chan].dimm->grain)); if (info->errsts & 0x0002) - edac_mc_handle_ue(mci, page, offst , row, "i82975x UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, offst, 0, + row, -1, -1, + "i82975x UE", "", NULL); else - edac_mc_handle_ce(mci, page, offst, info->derrsyn, row, - chan, "i82975x CE"); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, offst, info->derrsyn, + row, chan ? chan : 0, -1, + "i82975x CE", "", NULL); return 1; } @@ -370,8 +377,10 @@ static void i82975x_init_csrows(struct mem_ctl_info *mci, struct csrow_info *csrow; unsigned long last_cumul_size; u8 value; - u32 cumul_size; + u32 cumul_size, nr_pages; int index, chan; + struct dimm_info *dimm; + enum dev_type dtype; last_cumul_size = 0; @@ -400,28 +409,33 @@ static void i82975x_init_csrows(struct mem_ctl_info *mci, debugf3("%s(): (%d) cumul_size 0x%x\n", __func__, index, cumul_size); + nr_pages = cumul_size - last_cumul_size; + if (!nr_pages) + continue; + /* * Initialise dram labels * index values: * [0-7] for single-channel; i.e. csrow->nr_channels = 1 * [0-3] for dual-channel; i.e. csrow->nr_channels = 2 */ - for (chan = 0; chan < csrow->nr_channels; chan++) - strncpy(csrow->channels[chan].label, + dtype = i82975x_dram_type(mch_window, index); + for (chan = 0; chan < csrow->nr_channels; chan++) { + dimm = mci->csrows[index].channels[chan].dimm; + + dimm->nr_pages = nr_pages / csrow->nr_channels; + strncpy(csrow->channels[chan].dimm->label, labels[(index >> 1) + (chan * 2)], EDAC_MC_LABEL_LEN); - - if (cumul_size == last_cumul_size) - continue; /* not populated */ + dimm->grain = 1 << 7; /* 128Byte cache-line resolution */ + dimm->dtype = i82975x_dram_type(mch_window, index); + dimm->mtype = MEM_DDR2; /* I82975x supports only DDR2 */ + dimm->edac_mode = EDAC_SECDED; /* only supported */ + } csrow->first_page = last_cumul_size; csrow->last_page = cumul_size - 1; - csrow->nr_pages = cumul_size - last_cumul_size; last_cumul_size = cumul_size; - csrow->grain = 1 << 7; /* 128Byte cache-line resolution */ - csrow->mtype = MEM_DDR2; /* I82975x supports only DDR2 */ - csrow->dtype = i82975x_dram_type(mch_window, index); - csrow->edac_mode = EDAC_SECDED; /* only supported */ } } @@ -463,6 +477,7 @@ static int i82975x_probe1(struct pci_dev *pdev, int dev_idx) { int rc = -ENODEV; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct i82975x_pvt *pvt; void __iomem *mch_window; u32 mchbar; @@ -531,8 +546,13 @@ static int i82975x_probe1(struct pci_dev *pdev, int dev_idx) chans = dual_channel_active(mch_window) + 1; /* assuming only one controller, index thus is 0 */ - mci = edac_mc_alloc(sizeof(*pvt), I82975X_NR_CSROWS(chans), - chans, 0); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = I82975X_NR_DIMMS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = I82975X_NR_CSROWS(chans); + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (!mci) { rc = -ENOMEM; goto fail1; diff --git a/drivers/edac/mce_amd.h b/drivers/edac/mce_amd.h index c6074c5cd1e..8c87a5e8705 100644 --- a/drivers/edac/mce_amd.h +++ b/drivers/edac/mce_amd.h @@ -5,8 +5,6 @@ #include <asm/mce.h> -#define BIT_64(n) (U64_C(1) << (n)) - #define EC(x) ((x) & 0xffff) #define XEC(x, mask) (((x) >> 16) & mask) diff --git a/drivers/edac/mpc85xx_edac.c b/drivers/edac/mpc85xx_edac.c index 73464a62adf..4c402353ba9 100644 --- a/drivers/edac/mpc85xx_edac.c +++ b/drivers/edac/mpc85xx_edac.c @@ -854,12 +854,16 @@ static void mpc85xx_mc_check(struct mem_ctl_info *mci) mpc85xx_mc_printk(mci, KERN_ERR, "PFN out of range!\n"); if (err_detect & DDR_EDE_SBE) - edac_mc_handle_ce(mci, pfn, err_addr & ~PAGE_MASK, - syndrome, row_index, 0, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + pfn, err_addr & ~PAGE_MASK, syndrome, + row_index, 0, -1, + mci->ctl_name, "", NULL); if (err_detect & DDR_EDE_MBE) - edac_mc_handle_ue(mci, pfn, err_addr & ~PAGE_MASK, - row_index, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + pfn, err_addr & ~PAGE_MASK, syndrome, + row_index, 0, -1, + mci->ctl_name, "", NULL); out_be32(pdata->mc_vbase + MPC85XX_MC_ERR_DETECT, err_detect); } @@ -883,6 +887,7 @@ static void __devinit mpc85xx_init_csrows(struct mem_ctl_info *mci) { struct mpc85xx_mc_pdata *pdata = mci->pvt_info; struct csrow_info *csrow; + struct dimm_info *dimm; u32 sdram_ctl; u32 sdtype; enum mem_type mtype; @@ -929,6 +934,8 @@ static void __devinit mpc85xx_init_csrows(struct mem_ctl_info *mci) u32 end; csrow = &mci->csrows[index]; + dimm = csrow->channels[0].dimm; + cs_bnds = in_be32(pdata->mc_vbase + MPC85XX_MC_CS_BNDS_0 + (index * MPC85XX_MC_CS_BNDS_OFS)); @@ -944,19 +951,21 @@ static void __devinit mpc85xx_init_csrows(struct mem_ctl_info *mci) csrow->first_page = start; csrow->last_page = end; - csrow->nr_pages = end + 1 - start; - csrow->grain = 8; - csrow->mtype = mtype; - csrow->dtype = DEV_UNKNOWN; + + dimm->nr_pages = end + 1 - start; + dimm->grain = 8; + dimm->mtype = mtype; + dimm->dtype = DEV_UNKNOWN; if (sdram_ctl & DSC_X32_EN) - csrow->dtype = DEV_X32; - csrow->edac_mode = EDAC_SECDED; + dimm->dtype = DEV_X32; + dimm->edac_mode = EDAC_SECDED; } } static int __devinit mpc85xx_mc_err_probe(struct platform_device *op) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct mpc85xx_mc_pdata *pdata; struct resource r; u32 sdram_ctl; @@ -965,7 +974,13 @@ static int __devinit mpc85xx_mc_err_probe(struct platform_device *op) if (!devres_open_group(&op->dev, mpc85xx_mc_err_probe, GFP_KERNEL)) return -ENOMEM; - mci = edac_mc_alloc(sizeof(*pdata), 4, 1, edac_mc_idx); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = 4; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = 1; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(edac_mc_idx, ARRAY_SIZE(layers), sizeof(*pdata)); if (!mci) { devres_release_group(&op->dev, mpc85xx_mc_err_probe); return -ENOMEM; diff --git a/drivers/edac/mv64x60_edac.c b/drivers/edac/mv64x60_edac.c index 7e5ff367705..b0bb5a3d252 100644 --- a/drivers/edac/mv64x60_edac.c +++ b/drivers/edac/mv64x60_edac.c @@ -611,12 +611,17 @@ static void mv64x60_mc_check(struct mem_ctl_info *mci) /* first bit clear in ECC Err Reg, 1 bit error, correctable by HW */ if (!(reg & 0x1)) - edac_mc_handle_ce(mci, err_addr >> PAGE_SHIFT, - err_addr & PAGE_MASK, syndrome, 0, 0, - mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + err_addr >> PAGE_SHIFT, + err_addr & PAGE_MASK, syndrome, + 0, 0, -1, + mci->ctl_name, "", NULL); else /* 2 bit error, UE */ - edac_mc_handle_ue(mci, err_addr >> PAGE_SHIFT, - err_addr & PAGE_MASK, 0, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + err_addr >> PAGE_SHIFT, + err_addr & PAGE_MASK, 0, + 0, 0, -1, + mci->ctl_name, "", NULL); /* clear the error */ out_le32(pdata->mc_vbase + MV64X60_SDRAM_ERR_ADDR, 0); @@ -656,6 +661,8 @@ static void mv64x60_init_csrows(struct mem_ctl_info *mci, struct mv64x60_mc_pdata *pdata) { struct csrow_info *csrow; + struct dimm_info *dimm; + u32 devtype; u32 ctl; @@ -664,35 +671,36 @@ static void mv64x60_init_csrows(struct mem_ctl_info *mci, ctl = in_le32(pdata->mc_vbase + MV64X60_SDRAM_CONFIG); csrow = &mci->csrows[0]; - csrow->first_page = 0; - csrow->nr_pages = pdata->total_mem >> PAGE_SHIFT; - csrow->last_page = csrow->first_page + csrow->nr_pages - 1; - csrow->grain = 8; + dimm = csrow->channels[0].dimm; + + dimm->nr_pages = pdata->total_mem >> PAGE_SHIFT; + dimm->grain = 8; - csrow->mtype = (ctl & MV64X60_SDRAM_REGISTERED) ? MEM_RDDR : MEM_DDR; + dimm->mtype = (ctl & MV64X60_SDRAM_REGISTERED) ? MEM_RDDR : MEM_DDR; devtype = (ctl >> 20) & 0x3; switch (devtype) { case 0x0: - csrow->dtype = DEV_X32; + dimm->dtype = DEV_X32; break; case 0x2: /* could be X8 too, but no way to tell */ - csrow->dtype = DEV_X16; + dimm->dtype = DEV_X16; break; case 0x3: - csrow->dtype = DEV_X4; + dimm->dtype = DEV_X4; break; default: - csrow->dtype = DEV_UNKNOWN; + dimm->dtype = DEV_UNKNOWN; break; } - csrow->edac_mode = EDAC_SECDED; + dimm->edac_mode = EDAC_SECDED; } static int __devinit mv64x60_mc_err_probe(struct platform_device *pdev) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct mv64x60_mc_pdata *pdata; struct resource *r; u32 ctl; @@ -701,7 +709,14 @@ static int __devinit mv64x60_mc_err_probe(struct platform_device *pdev) if (!devres_open_group(&pdev->dev, mv64x60_mc_err_probe, GFP_KERNEL)) return -ENOMEM; - mci = edac_mc_alloc(sizeof(struct mv64x60_mc_pdata), 1, 1, edac_mc_idx); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = 1; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = 1; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(edac_mc_idx, ARRAY_SIZE(layers), layers, + sizeof(struct mv64x60_mc_pdata)); if (!mci) { printk(KERN_ERR "%s: No memory for CPU err\n", __func__); devres_release_group(&pdev->dev, mv64x60_mc_err_probe); diff --git a/drivers/edac/pasemi_edac.c b/drivers/edac/pasemi_edac.c index 7f71ee43674..b095a906a99 100644 --- a/drivers/edac/pasemi_edac.c +++ b/drivers/edac/pasemi_edac.c @@ -110,15 +110,16 @@ static void pasemi_edac_process_error_info(struct mem_ctl_info *mci, u32 errsta) /* uncorrectable/multi-bit errors */ if (errsta & (MCDEBUG_ERRSTA_MBE_STATUS | MCDEBUG_ERRSTA_RFL_STATUS)) { - edac_mc_handle_ue(mci, mci->csrows[cs].first_page, 0, - cs, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + mci->csrows[cs].first_page, 0, 0, + cs, 0, -1, mci->ctl_name, "", NULL); } /* correctable/single-bit errors */ - if (errsta & MCDEBUG_ERRSTA_SBE_STATUS) { - edac_mc_handle_ce(mci, mci->csrows[cs].first_page, 0, - 0, cs, 0, mci->ctl_name); - } + if (errsta & MCDEBUG_ERRSTA_SBE_STATUS) + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + mci->csrows[cs].first_page, 0, 0, + cs, 0, -1, mci->ctl_name, "", NULL); } static void pasemi_edac_check(struct mem_ctl_info *mci) @@ -135,11 +136,13 @@ static int pasemi_edac_init_csrows(struct mem_ctl_info *mci, enum edac_type edac_mode) { struct csrow_info *csrow; + struct dimm_info *dimm; u32 rankcfg; int index; for (index = 0; index < mci->nr_csrows; index++) { csrow = &mci->csrows[index]; + dimm = csrow->channels[0].dimm; pci_read_config_dword(pdev, MCDRAM_RANKCFG + (index * 12), @@ -151,20 +154,20 @@ static int pasemi_edac_init_csrows(struct mem_ctl_info *mci, switch ((rankcfg & MCDRAM_RANKCFG_TYPE_SIZE_M) >> MCDRAM_RANKCFG_TYPE_SIZE_S) { case 0: - csrow->nr_pages = 128 << (20 - PAGE_SHIFT); + dimm->nr_pages = 128 << (20 - PAGE_SHIFT); break; case 1: - csrow->nr_pages = 256 << (20 - PAGE_SHIFT); + dimm->nr_pages = 256 << (20 - PAGE_SHIFT); break; case 2: case 3: - csrow->nr_pages = 512 << (20 - PAGE_SHIFT); + dimm->nr_pages = 512 << (20 - PAGE_SHIFT); break; case 4: - csrow->nr_pages = 1024 << (20 - PAGE_SHIFT); + dimm->nr_pages = 1024 << (20 - PAGE_SHIFT); break; case 5: - csrow->nr_pages = 2048 << (20 - PAGE_SHIFT); + dimm->nr_pages = 2048 << (20 - PAGE_SHIFT); break; default: edac_mc_printk(mci, KERN_ERR, @@ -174,13 +177,13 @@ static int pasemi_edac_init_csrows(struct mem_ctl_info *mci, } csrow->first_page = last_page_in_mmc; - csrow->last_page = csrow->first_page + csrow->nr_pages - 1; - last_page_in_mmc += csrow->nr_pages; + csrow->last_page = csrow->first_page + dimm->nr_pages - 1; + last_page_in_mmc += dimm->nr_pages; csrow->page_mask = 0; - csrow->grain = PASEMI_EDAC_ERROR_GRAIN; - csrow->mtype = MEM_DDR; - csrow->dtype = DEV_UNKNOWN; - csrow->edac_mode = edac_mode; + dimm->grain = PASEMI_EDAC_ERROR_GRAIN; + dimm->mtype = MEM_DDR; + dimm->dtype = DEV_UNKNOWN; + dimm->edac_mode = edac_mode; } return 0; } @@ -189,6 +192,7 @@ static int __devinit pasemi_edac_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct mem_ctl_info *mci = NULL; + struct edac_mc_layer layers[2]; u32 errctl1, errcor, scrub, mcen; pci_read_config_dword(pdev, MCCFG_MCEN, &mcen); @@ -205,9 +209,14 @@ static int __devinit pasemi_edac_probe(struct pci_dev *pdev, MCDEBUG_ERRCTL1_RFL_LOG_EN; pci_write_config_dword(pdev, MCDEBUG_ERRCTL1, errctl1); - mci = edac_mc_alloc(0, PASEMI_EDAC_NR_CSROWS, PASEMI_EDAC_NR_CHANS, - system_mmc_id++); - + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = PASEMI_EDAC_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = PASEMI_EDAC_NR_CHANS; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(system_mmc_id++, ARRAY_SIZE(layers), layers, + 0); if (mci == NULL) return -ENOMEM; diff --git a/drivers/edac/ppc4xx_edac.c b/drivers/edac/ppc4xx_edac.c index d427c69bb8b..f3f9fed06ad 100644 --- a/drivers/edac/ppc4xx_edac.c +++ b/drivers/edac/ppc4xx_edac.c @@ -727,7 +727,10 @@ ppc4xx_edac_handle_ce(struct mem_ctl_info *mci, for (row = 0; row < mci->nr_csrows; row++) if (ppc4xx_edac_check_bank_error(status, row)) - edac_mc_handle_ce_no_info(mci, message); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + 0, 0, 0, + row, 0, -1, + message, "", NULL); } /** @@ -755,7 +758,10 @@ ppc4xx_edac_handle_ue(struct mem_ctl_info *mci, for (row = 0; row < mci->nr_csrows; row++) if (ppc4xx_edac_check_bank_error(status, row)) - edac_mc_handle_ue(mci, page, offset, row, message); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, offset, 0, + row, 0, -1, + message, "", NULL); } /** @@ -895,9 +901,8 @@ ppc4xx_edac_init_csrows(struct mem_ctl_info *mci, u32 mcopt1) enum mem_type mtype; enum dev_type dtype; enum edac_type edac_mode; - int row; - u32 mbxcf, size; - static u32 ppc4xx_last_page; + int row, j; + u32 mbxcf, size, nr_pages; /* Establish the memory type and width */ @@ -948,7 +953,7 @@ ppc4xx_edac_init_csrows(struct mem_ctl_info *mci, u32 mcopt1) case SDRAM_MBCF_SZ_2GB: case SDRAM_MBCF_SZ_4GB: case SDRAM_MBCF_SZ_8GB: - csi->nr_pages = SDRAM_MBCF_SZ_TO_PAGES(size); + nr_pages = SDRAM_MBCF_SZ_TO_PAGES(size); break; default: ppc4xx_edac_mc_printk(KERN_ERR, mci, @@ -959,10 +964,6 @@ ppc4xx_edac_init_csrows(struct mem_ctl_info *mci, u32 mcopt1) goto done; } - csi->first_page = ppc4xx_last_page; - csi->last_page = csi->first_page + csi->nr_pages - 1; - csi->page_mask = 0; - /* * It's unclear exactly what grain should be set to * here. The SDRAM_ECCES register allows resolution of @@ -975,15 +976,17 @@ ppc4xx_edac_init_csrows(struct mem_ctl_info *mci, u32 mcopt1) * possible values would be the PLB width (16), the * page size (PAGE_SIZE) or the memory width (2 or 4). */ + for (j = 0; j < csi->nr_channels; j++) { + struct dimm_info *dimm = csi->channels[j].dimm; - csi->grain = 1; - - csi->mtype = mtype; - csi->dtype = dtype; + dimm->nr_pages = nr_pages / csi->nr_channels; + dimm->grain = 1; - csi->edac_mode = edac_mode; + dimm->mtype = mtype; + dimm->dtype = dtype; - ppc4xx_last_page += csi->nr_pages; + dimm->edac_mode = edac_mode; + } } done: @@ -1236,6 +1239,7 @@ static int __devinit ppc4xx_edac_probe(struct platform_device *op) dcr_host_t dcr_host; const struct device_node *np = op->dev.of_node; struct mem_ctl_info *mci = NULL; + struct edac_mc_layer layers[2]; static int ppc4xx_edac_instance; /* @@ -1281,12 +1285,14 @@ static int __devinit ppc4xx_edac_probe(struct platform_device *op) * controller instance and perform the appropriate * initialization. */ - - mci = edac_mc_alloc(sizeof(struct ppc4xx_edac_pdata), - ppc4xx_edac_nr_csrows, - ppc4xx_edac_nr_chans, - ppc4xx_edac_instance); - + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = ppc4xx_edac_nr_csrows; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = ppc4xx_edac_nr_chans; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(ppc4xx_edac_instance, ARRAY_SIZE(layers), layers, + sizeof(struct ppc4xx_edac_pdata)); if (mci == NULL) { ppc4xx_edac_printk(KERN_ERR, "%s: " "Failed to allocate EDAC MC instance!\n", diff --git a/drivers/edac/r82600_edac.c b/drivers/edac/r82600_edac.c index 6d908ad72d6..e1cacd164f3 100644 --- a/drivers/edac/r82600_edac.c +++ b/drivers/edac/r82600_edac.c @@ -179,10 +179,11 @@ static int r82600_process_error_info(struct mem_ctl_info *mci, error_found = 1; if (handle_errors) - edac_mc_handle_ce(mci, page, 0, /* not avail */ - syndrome, - edac_mc_find_csrow_by_page(mci, page), - 0, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + page, 0, syndrome, + edac_mc_find_csrow_by_page(mci, page), + 0, -1, + mci->ctl_name, "", NULL); } if (info->eapr & BIT(1)) { /* UE? */ @@ -190,9 +191,11 @@ static int r82600_process_error_info(struct mem_ctl_info *mci, if (handle_errors) /* 82600 doesn't give enough info */ - edac_mc_handle_ue(mci, page, 0, - edac_mc_find_csrow_by_page(mci, page), - mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + page, 0, 0, + edac_mc_find_csrow_by_page(mci, page), + 0, -1, + mci->ctl_name, "", NULL); } return error_found; @@ -216,6 +219,7 @@ static void r82600_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, u8 dramcr) { struct csrow_info *csrow; + struct dimm_info *dimm; int index; u8 drbar; /* SDRAM Row Boundary Address Register */ u32 row_high_limit, row_high_limit_last; @@ -227,6 +231,7 @@ static void r82600_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, for (index = 0; index < mci->nr_csrows; index++) { csrow = &mci->csrows[index]; + dimm = csrow->channels[0].dimm; /* find the DRAM Chip Select Base address and mask */ pci_read_config_byte(pdev, R82600_DRBA + index, &drbar); @@ -247,16 +252,17 @@ static void r82600_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, csrow->first_page = row_base >> PAGE_SHIFT; csrow->last_page = (row_high_limit >> PAGE_SHIFT) - 1; - csrow->nr_pages = csrow->last_page - csrow->first_page + 1; + + dimm->nr_pages = csrow->last_page - csrow->first_page + 1; /* Error address is top 19 bits - so granularity is * * 14 bits */ - csrow->grain = 1 << 14; - csrow->mtype = reg_sdram ? MEM_RDDR : MEM_DDR; + dimm->grain = 1 << 14; + dimm->mtype = reg_sdram ? MEM_RDDR : MEM_DDR; /* FIXME - check that this is unknowable with this chipset */ - csrow->dtype = DEV_UNKNOWN; + dimm->dtype = DEV_UNKNOWN; /* Mode is global on 82600 */ - csrow->edac_mode = ecc_on ? EDAC_SECDED : EDAC_NONE; + dimm->edac_mode = ecc_on ? EDAC_SECDED : EDAC_NONE; row_high_limit_last = row_high_limit; } } @@ -264,6 +270,7 @@ static void r82600_init_csrows(struct mem_ctl_info *mci, struct pci_dev *pdev, static int r82600_probe1(struct pci_dev *pdev, int dev_idx) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; u8 dramcr; u32 eapr; u32 scrub_disabled; @@ -278,8 +285,13 @@ static int r82600_probe1(struct pci_dev *pdev, int dev_idx) debugf2("%s(): sdram refresh rate = %#0x\n", __func__, sdram_refresh_rate); debugf2("%s(): DRAMC register = %#0x\n", __func__, dramcr); - mci = edac_mc_alloc(0, R82600_NR_CSROWS, R82600_NR_CHANS, 0); - + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = R82600_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = R82600_NR_CHANS; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); if (mci == NULL) return -ENOMEM; diff --git a/drivers/edac/sb_edac.c b/drivers/edac/sb_edac.c index 123204f8e23..4adaf4b7da9 100644 --- a/drivers/edac/sb_edac.c +++ b/drivers/edac/sb_edac.c @@ -314,8 +314,6 @@ struct sbridge_pvt { struct sbridge_info info; struct sbridge_channel channel[NUM_CHANNELS]; - int csrow_map[NUM_CHANNELS][MAX_DIMMS]; - /* Memory type detection */ bool is_mirrored, is_lockstep, is_close_pg; @@ -487,29 +485,14 @@ static struct pci_dev *get_pdev_slot_func(u8 bus, unsigned slot, } /** - * sbridge_get_active_channels() - gets the number of channels and csrows + * check_if_ecc_is_active() - Checks if ECC is active * bus: Device bus - * @channels: Number of channels that will be returned - * @csrows: Number of csrows found - * - * Since EDAC core needs to know in advance the number of available channels - * and csrows, in order to allocate memory for csrows/channels, it is needed - * to run two similar steps. At the first step, implemented on this function, - * it checks the number of csrows/channels present at one socket, identified - * by the associated PCI bus. - * this is used in order to properly allocate the size of mci components. - * Note: one csrow is one dimm. */ -static int sbridge_get_active_channels(const u8 bus, unsigned *channels, - unsigned *csrows) +static int check_if_ecc_is_active(const u8 bus) { struct pci_dev *pdev = NULL; - int i, j; u32 mcmtr; - *channels = 0; - *csrows = 0; - pdev = get_pdev_slot_func(bus, 15, 0); if (!pdev) { sbridge_printk(KERN_ERR, "Couldn't find PCI device " @@ -523,41 +506,14 @@ static int sbridge_get_active_channels(const u8 bus, unsigned *channels, sbridge_printk(KERN_ERR, "ECC is disabled. Aborting\n"); return -ENODEV; } - - for (i = 0; i < NUM_CHANNELS; i++) { - u32 mtr; - - /* Device 15 functions 2 - 5 */ - pdev = get_pdev_slot_func(bus, 15, 2 + i); - if (!pdev) { - sbridge_printk(KERN_ERR, "Couldn't find PCI device " - "%2x.%02d.%d!!!\n", - bus, 15, 2 + i); - return -ENODEV; - } - (*channels)++; - - for (j = 0; j < ARRAY_SIZE(mtr_regs); j++) { - pci_read_config_dword(pdev, mtr_regs[j], &mtr); - debugf1("Bus#%02x channel #%d MTR%d = %x\n", bus, i, j, mtr); - if (IS_DIMM_PRESENT(mtr)) - (*csrows)++; - } - } - - debugf0("Number of active channels: %d, number of active dimms: %d\n", - *channels, *csrows); - return 0; } -static int get_dimm_config(const struct mem_ctl_info *mci) +static int get_dimm_config(struct mem_ctl_info *mci) { struct sbridge_pvt *pvt = mci->pvt_info; - struct csrow_info *csr; + struct dimm_info *dimm; int i, j, banks, ranks, rows, cols, size, npages; - int csrow = 0; - unsigned long last_page = 0; u32 reg; enum edac_type mode; enum mem_type mtype; @@ -616,6 +572,8 @@ static int get_dimm_config(const struct mem_ctl_info *mci) u32 mtr; for (j = 0; j < ARRAY_SIZE(mtr_regs); j++) { + dimm = EDAC_DIMM_PTR(mci->layers, mci->dimms, mci->n_layers, + i, j, 0); pci_read_config_dword(pvt->pci_tad[i], mtr_regs[j], &mtr); debugf4("Channel #%d MTR%d = %x\n", i, j, mtr); @@ -634,29 +592,15 @@ static int get_dimm_config(const struct mem_ctl_info *mci) pvt->sbridge_dev->mc, i, j, size, npages, banks, ranks, rows, cols); - csr = &mci->csrows[csrow]; - - csr->first_page = last_page; - csr->last_page = last_page + npages - 1; - csr->page_mask = 0UL; /* Unused */ - csr->nr_pages = npages; - csr->grain = 32; - csr->csrow_idx = csrow; - csr->dtype = (banks == 8) ? DEV_X8 : DEV_X4; - csr->ce_count = 0; - csr->ue_count = 0; - csr->mtype = mtype; - csr->edac_mode = mode; - csr->nr_channels = 1; - csr->channels[0].chan_idx = i; - csr->channels[0].ce_count = 0; - pvt->csrow_map[i][j] = csrow; - snprintf(csr->channels[0].label, - sizeof(csr->channels[0].label), + + dimm->nr_pages = npages; + dimm->grain = 32; + dimm->dtype = (banks == 8) ? DEV_X8 : DEV_X4; + dimm->mtype = mtype; + dimm->edac_mode = mode; + snprintf(dimm->label, sizeof(dimm->label), "CPU_SrcID#%u_Channel#%u_DIMM#%u", pvt->sbridge_dev->source_id, i, j); - last_page += npages; - csrow++; } } } @@ -844,11 +788,10 @@ static int get_memory_error_data(struct mem_ctl_info *mci, u8 *socket, long *channel_mask, u8 *rank, - char *area_type) + char **area_type, char *msg) { struct mem_ctl_info *new_mci; struct sbridge_pvt *pvt = mci->pvt_info; - char msg[256]; int n_rir, n_sads, n_tads, sad_way, sck_xch; int sad_interl, idx, base_ch; int interleave_mode; @@ -870,12 +813,10 @@ static int get_memory_error_data(struct mem_ctl_info *mci, */ if ((addr > (u64) pvt->tolm) && (addr < (1LL << 32))) { sprintf(msg, "Error at TOLM area, on addr 0x%08Lx", addr); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } if (addr >= (u64)pvt->tohm) { sprintf(msg, "Error at MMIOH area, on addr 0x%016Lx", addr); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } @@ -892,7 +833,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, limit = SAD_LIMIT(reg); if (limit <= prv) { sprintf(msg, "Can't discover the memory socket"); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } if (addr <= limit) @@ -901,10 +841,9 @@ static int get_memory_error_data(struct mem_ctl_info *mci, } if (n_sads == MAX_SAD) { sprintf(msg, "Can't discover the memory socket"); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } - area_type = get_dram_attr(reg); + *area_type = get_dram_attr(reg); interleave_mode = INTERLEAVE_MODE(reg); pci_read_config_dword(pvt->pci_sad0, interleave_list[n_sads], @@ -942,7 +881,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, break; default: sprintf(msg, "Can't discover socket interleave"); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } *socket = sad_interleave[idx]; @@ -957,7 +895,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, if (!new_mci) { sprintf(msg, "Struct for socket #%u wasn't initialized", *socket); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } mci = new_mci; @@ -973,7 +910,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, limit = TAD_LIMIT(reg); if (limit <= prv) { sprintf(msg, "Can't discover the memory channel"); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } if (addr <= limit) @@ -1013,7 +949,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, break; default: sprintf(msg, "Can't discover the TAD target"); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } *channel_mask = 1 << base_ch; @@ -1027,7 +962,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, break; default: sprintf(msg, "Invalid mirror set. Can't decode addr"); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } } else @@ -1055,7 +989,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, if (offset > addr) { sprintf(msg, "Can't calculate ch addr: TAD offset 0x%08Lx is too high for addr 0x%08Lx!", offset, addr); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } addr -= offset; @@ -1095,7 +1028,6 @@ static int get_memory_error_data(struct mem_ctl_info *mci, if (n_rir == MAX_RIR_RANGES) { sprintf(msg, "Can't discover the memory rank for ch addr 0x%08Lx", ch_addr); - edac_mc_handle_ce_no_info(mci, msg); return -EINVAL; } rir_way = RIR_WAY(reg); @@ -1409,7 +1341,8 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci, { struct mem_ctl_info *new_mci; struct sbridge_pvt *pvt = mci->pvt_info; - char *type, *optype, *msg, *recoverable_msg; + enum hw_event_mc_err_type tp_event; + char *type, *optype, msg[256]; bool ripv = GET_BITFIELD(m->mcgstatus, 0, 0); bool overflow = GET_BITFIELD(m->status, 62, 62); bool uncorrected_error = GET_BITFIELD(m->status, 61, 61); @@ -1421,13 +1354,21 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci, u32 optypenum = GET_BITFIELD(m->status, 4, 6); long channel_mask, first_channel; u8 rank, socket; - int csrow, rc, dimm; - char *area_type = "Unknown"; - - if (ripv) - type = "NON_FATAL"; - else - type = "FATAL"; + int rc, dimm; + char *area_type = NULL; + + if (uncorrected_error) { + if (ripv) { + type = "FATAL"; + tp_event = HW_EVENT_ERR_FATAL; + } else { + type = "NON_FATAL"; + tp_event = HW_EVENT_ERR_UNCORRECTED; + } + } else { + type = "CORRECTED"; + tp_event = HW_EVENT_ERR_CORRECTED; + } /* * According with Table 15-9 of the Intel Architecture spec vol 3A, @@ -1445,19 +1386,19 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci, } else { switch (optypenum) { case 0: - optype = "generic undef request"; + optype = "generic undef request error"; break; case 1: - optype = "memory read"; + optype = "memory read error"; break; case 2: - optype = "memory write"; + optype = "memory write error"; break; case 3: - optype = "addr/cmd"; + optype = "addr/cmd error"; break; case 4: - optype = "memory scrubbing"; + optype = "memory scrubbing error"; break; default: optype = "reserved"; @@ -1466,13 +1407,13 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci, } rc = get_memory_error_data(mci, m->addr, &socket, - &channel_mask, &rank, area_type); + &channel_mask, &rank, &area_type, msg); if (rc < 0) - return; + goto err_parsing; new_mci = get_mci_for_node_id(socket); if (!new_mci) { - edac_mc_handle_ce_no_info(mci, "Error: socket got corrupted!"); - return; + strcpy(msg, "Error: socket got corrupted!"); + goto err_parsing; } mci = new_mci; pvt = mci->pvt_info; @@ -1486,45 +1427,39 @@ static void sbridge_mce_output_error(struct mem_ctl_info *mci, else dimm = 2; - csrow = pvt->csrow_map[first_channel][dimm]; - - if (uncorrected_error && recoverable) - recoverable_msg = " recoverable"; - else - recoverable_msg = ""; /* - * FIXME: What should we do with "channel" information on mcelog? - * Probably, we can just discard it, as the channel information - * comes from the get_memory_error_data() address decoding + * FIXME: On some memory configurations (mirror, lockstep), the + * Memory Controller can't point the error to a single DIMM. The + * EDAC core should be handling the channel mask, in order to point + * to the group of dimm's where the error may be happening. */ - msg = kasprintf(GFP_ATOMIC, - "%d %s error(s): %s on %s area %s%s: cpu=%d Err=%04x:%04x (ch=%d), " - "addr = 0x%08llx => socket=%d, Channel=%ld(mask=%ld), rank=%d\n", - core_err_cnt, - area_type, - optype, - type, - recoverable_msg, - overflow ? "OVERFLOW" : "", - m->cpu, - mscod, errcode, - channel, /* 1111b means not specified */ - (long long) m->addr, - socket, - first_channel, /* This is the real channel on SB */ - channel_mask, - rank); + snprintf(msg, sizeof(msg), + "count:%d%s%s area:%s err_code:%04x:%04x socket:%d channel_mask:%ld rank:%d", + core_err_cnt, + overflow ? " OVERFLOW" : "", + (uncorrected_error && recoverable) ? " recoverable" : "", + area_type, + mscod, errcode, + socket, + channel_mask, + rank); debugf0("%s", msg); + /* FIXME: need support for channel mask */ + /* Call the helper to output message */ - if (uncorrected_error) - edac_mc_handle_fbd_ue(mci, csrow, 0, 0, msg); - else - edac_mc_handle_fbd_ce(mci, csrow, 0, msg); + edac_mc_handle_error(tp_event, mci, + m->addr >> PAGE_SHIFT, m->addr & ~PAGE_MASK, 0, + channel, dimm, -1, + optype, msg, m); + return; +err_parsing: + edac_mc_handle_error(tp_event, mci, 0, 0, 0, + -1, -1, -1, + msg, "", m); - kfree(msg); } /* @@ -1683,16 +1618,25 @@ static void sbridge_unregister_mci(struct sbridge_dev *sbridge_dev) static int sbridge_register_mci(struct sbridge_dev *sbridge_dev) { struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct sbridge_pvt *pvt; - int rc, channels, csrows; + int rc; /* Check the number of active and not disabled channels */ - rc = sbridge_get_active_channels(sbridge_dev->bus, &channels, &csrows); + rc = check_if_ecc_is_active(sbridge_dev->bus); if (unlikely(rc < 0)) return rc; /* allocate a new MC control structure */ - mci = edac_mc_alloc(sizeof(*pvt), csrows, channels, sbridge_dev->mc); + layers[0].type = EDAC_MC_LAYER_CHANNEL; + layers[0].size = NUM_CHANNELS; + layers[0].is_virt_csrow = false; + layers[1].type = EDAC_MC_LAYER_SLOT; + layers[1].size = MAX_DIMMS; + layers[1].is_virt_csrow = true; + mci = edac_mc_alloc(sbridge_dev->mc, ARRAY_SIZE(layers), layers, + sizeof(*pvt)); + if (unlikely(!mci)) return -ENOMEM; diff --git a/drivers/edac/tile_edac.c b/drivers/edac/tile_edac.c index e99d0097618..7bb4614730d 100644 --- a/drivers/edac/tile_edac.c +++ b/drivers/edac/tile_edac.c @@ -71,7 +71,10 @@ static void tile_edac_check(struct mem_ctl_info *mci) if (mem_error.sbe_count != priv->ce_count) { dev_dbg(mci->dev, "ECC CE err on node %d\n", priv->node); priv->ce_count = mem_error.sbe_count; - edac_mc_handle_ce(mci, 0, 0, 0, 0, 0, mci->ctl_name); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + 0, 0, 0, + 0, 0, -1, + mci->ctl_name, "", NULL); } } @@ -84,6 +87,7 @@ static int __devinit tile_edac_init_csrows(struct mem_ctl_info *mci) struct csrow_info *csrow = &mci->csrows[0]; struct tile_edac_priv *priv = mci->pvt_info; struct mshim_mem_info mem_info; + struct dimm_info *dimm = csrow->channels[0].dimm; if (hv_dev_pread(priv->hv_devhdl, 0, (HV_VirtAddr)&mem_info, sizeof(struct mshim_mem_info), MSHIM_MEM_INFO_OFF) != @@ -93,27 +97,25 @@ static int __devinit tile_edac_init_csrows(struct mem_ctl_info *mci) } if (mem_info.mem_ecc) - csrow->edac_mode = EDAC_SECDED; + dimm->edac_mode = EDAC_SECDED; else - csrow->edac_mode = EDAC_NONE; + dimm->edac_mode = EDAC_NONE; switch (mem_info.mem_type) { case DDR2: - csrow->mtype = MEM_DDR2; + dimm->mtype = MEM_DDR2; break; case DDR3: - csrow->mtype = MEM_DDR3; + dimm->mtype = MEM_DDR3; break; default: return -1; } - csrow->first_page = 0; - csrow->nr_pages = mem_info.mem_size >> PAGE_SHIFT; - csrow->last_page = csrow->first_page + csrow->nr_pages - 1; - csrow->grain = TILE_EDAC_ERROR_GRAIN; - csrow->dtype = DEV_UNKNOWN; + dimm->nr_pages = mem_info.mem_size >> PAGE_SHIFT; + dimm->grain = TILE_EDAC_ERROR_GRAIN; + dimm->dtype = DEV_UNKNOWN; return 0; } @@ -123,6 +125,7 @@ static int __devinit tile_edac_mc_probe(struct platform_device *pdev) char hv_file[32]; int hv_devhdl; struct mem_ctl_info *mci; + struct edac_mc_layer layers[2]; struct tile_edac_priv *priv; int rc; @@ -132,8 +135,14 @@ static int __devinit tile_edac_mc_probe(struct platform_device *pdev) return -EINVAL; /* A TILE MC has a single channel and one chip-select row. */ - mci = edac_mc_alloc(sizeof(struct tile_edac_priv), - TILE_EDAC_NR_CSROWS, TILE_EDAC_NR_CHANS, pdev->id); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = TILE_EDAC_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = TILE_EDAC_NR_CHANS; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(pdev->id, ARRAY_SIZE(layers), layers, + sizeof(struct tile_edac_priv)); if (mci == NULL) return -ENOMEM; priv = mci->pvt_info; diff --git a/drivers/edac/x38_edac.c b/drivers/edac/x38_edac.c index a438297389e..1ac7962d63e 100644 --- a/drivers/edac/x38_edac.c +++ b/drivers/edac/x38_edac.c @@ -215,19 +215,26 @@ static void x38_process_error_info(struct mem_ctl_info *mci, return; if ((info->errsts ^ info->errsts2) & X38_ERRSTS_BITS) { - edac_mc_handle_ce_no_info(mci, "UE overwrote CE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 0, 0, 0, + -1, -1, -1, + "UE overwrote CE", "", NULL); info->errsts = info->errsts2; } for (channel = 0; channel < x38_channel_num; channel++) { log = info->eccerrlog[channel]; if (log & X38_ECCERRLOG_UE) { - edac_mc_handle_ue(mci, 0, 0, - eccerrlog_row(channel, log), "x38 UE"); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + 0, 0, 0, + eccerrlog_row(channel, log), + -1, -1, + "x38 UE", "", NULL); } else if (log & X38_ECCERRLOG_CE) { - edac_mc_handle_ce(mci, 0, 0, - eccerrlog_syndrome(log), - eccerrlog_row(channel, log), 0, "x38 CE"); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + 0, 0, eccerrlog_syndrome(log), + eccerrlog_row(channel, log), + -1, -1, + "x38 CE", "", NULL); } } } @@ -317,9 +324,9 @@ static unsigned long drb_to_nr_pages( static int x38_probe1(struct pci_dev *pdev, int dev_idx) { int rc; - int i; + int i, j; struct mem_ctl_info *mci = NULL; - unsigned long last_page; + struct edac_mc_layer layers[2]; u16 drbs[X38_CHANNELS][X38_RANKS_PER_CHANNEL]; bool stacked; void __iomem *window; @@ -335,7 +342,13 @@ static int x38_probe1(struct pci_dev *pdev, int dev_idx) how_many_channel(pdev); /* FIXME: unconventional pvt_info usage */ - mci = edac_mc_alloc(0, X38_RANKS, x38_channel_num, 0); + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = X38_RANKS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = x38_channel_num; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); if (!mci) return -ENOMEM; @@ -363,7 +376,6 @@ static int x38_probe1(struct pci_dev *pdev, int dev_idx) * cumulative; the last one will contain the total memory * contained in all ranks. */ - last_page = -1UL; for (i = 0; i < mci->nr_csrows; i++) { unsigned long nr_pages; struct csrow_info *csrow = &mci->csrows[i]; @@ -372,20 +384,18 @@ static int x38_probe1(struct pci_dev *pdev, int dev_idx) i / X38_RANKS_PER_CHANNEL, i % X38_RANKS_PER_CHANNEL); - if (nr_pages == 0) { - csrow->mtype = MEM_EMPTY; + if (nr_pages == 0) continue; - } - csrow->first_page = last_page + 1; - last_page += nr_pages; - csrow->last_page = last_page; - csrow->nr_pages = nr_pages; + for (j = 0; j < x38_channel_num; j++) { + struct dimm_info *dimm = csrow->channels[j].dimm; - csrow->grain = nr_pages << PAGE_SHIFT; - csrow->mtype = MEM_DDR2; - csrow->dtype = DEV_UNKNOWN; - csrow->edac_mode = EDAC_UNKNOWN; + dimm->nr_pages = nr_pages / x38_channel_num; + dimm->grain = nr_pages << PAGE_SHIFT; + dimm->mtype = MEM_DDR2; + dimm->dtype = DEV_UNKNOWN; + dimm->edac_mode = EDAC_UNKNOWN; + } } x38_clear_error_info(mci); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 0356099ae04..c4067d0141f 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -114,6 +114,14 @@ config GPIO_EP93XX depends on ARCH_EP93XX select GPIO_GENERIC +config GPIO_MM_LANTIQ + bool "Lantiq Memory mapped GPIOs" + depends on LANTIQ && SOC_XWAY + help + This enables support for memory mapped GPIOs on the External Bus Unit + (EBU) found on Lantiq SoCs. The gpios are output only as they are + created by attaching a 16bit latch to the bus. + config GPIO_MPC5200 def_bool y depends on PPC_MPC52xx @@ -358,6 +366,16 @@ config GPIO_STMPE This enables support for the GPIOs found on the STMPE I/O Expanders. +config GPIO_STP_XWAY + bool "XWAY STP GPIOs" + depends on SOC_XWAY + help + This enables support for the Serial To Parallel (STP) unit found on + XWAY SoC. The STP allows the SoC to drive a shift registers cascade, + that can be up to 24 bit. This peripheral is aimed at driving leds. + Some of the gpios/leds can be auto updated by the soc with dsl and + phy status. + config GPIO_TC3589X bool "TC3589X GPIOs" depends on MFD_TC3589X diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fde36e5e353..0f55662002c 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o obj-$(CONFIG_GPIO_MC9S08DZ60) += gpio-mc9s08dz60.o obj-$(CONFIG_GPIO_MCP23S08) += gpio-mcp23s08.o obj-$(CONFIG_GPIO_ML_IOH) += gpio-ml-ioh.o +obj-$(CONFIG_GPIO_MM_LANTIQ) += gpio-mm-lantiq.o obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o @@ -54,6 +55,7 @@ obj-$(CONFIG_GPIO_SCH) += gpio-sch.o obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o obj-$(CONFIG_GPIO_STA2X11) += gpio-sta2x11.o obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o +obj-$(CONFIG_GPIO_STP_XWAY) += gpio-stp-xway.o obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o obj-$(CONFIG_ARCH_TEGRA) += gpio-tegra.o diff --git a/drivers/gpio/gpio-mm-lantiq.c b/drivers/gpio/gpio-mm-lantiq.c new file mode 100644 index 00000000000..2983dfbd066 --- /dev/null +++ b/drivers/gpio/gpio-mm-lantiq.c @@ -0,0 +1,158 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Copyright (C) 2012 John Crispin <blogic@openwrt.org> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include <lantiq_soc.h> + +/* + * By attaching hardware latches to the EBU it is possible to create output + * only gpios. This driver configures a special memory address, which when + * written to outputs 16 bit to the latches. + */ + +#define LTQ_EBU_BUSCON 0x1e7ff /* 16 bit access, slowest timing */ +#define LTQ_EBU_WP 0x80000000 /* write protect bit */ + +struct ltq_mm { + struct of_mm_gpio_chip mmchip; + u16 shadow; /* shadow the latches state */ +}; + +/** + * ltq_mm_apply() - write the shadow value to the ebu address. + * @chip: Pointer to our private data structure. + * + * Write the shadow value to the EBU to set the gpios. We need to set the + * global EBU lock to make sure that PCI/MTD dont break. + */ +static void ltq_mm_apply(struct ltq_mm *chip) +{ + unsigned long flags; + + spin_lock_irqsave(&ebu_lock, flags); + ltq_ebu_w32(LTQ_EBU_BUSCON, LTQ_EBU_BUSCON1); + __raw_writew(chip->shadow, chip->mmchip.regs); + ltq_ebu_w32(LTQ_EBU_BUSCON | LTQ_EBU_WP, LTQ_EBU_BUSCON1); + spin_unlock_irqrestore(&ebu_lock, flags); +} + +/** + * ltq_mm_set() - gpio_chip->set - set gpios. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * @val: Value to be written to specified signal. + * + * Set the shadow value and call ltq_mm_apply. + */ +static void ltq_mm_set(struct gpio_chip *gc, unsigned offset, int value) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct ltq_mm *chip = + container_of(mm_gc, struct ltq_mm, mmchip); + + if (value) + chip->shadow |= (1 << offset); + else + chip->shadow &= ~(1 << offset); + ltq_mm_apply(chip); +} + +/** + * ltq_mm_dir_out() - gpio_chip->dir_out - set gpio direction. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * @val: Value to be written to specified signal. + * + * Same as ltq_mm_set, always returns 0. + */ +static int ltq_mm_dir_out(struct gpio_chip *gc, unsigned offset, int value) +{ + ltq_mm_set(gc, offset, value); + + return 0; +} + +/** + * ltq_mm_save_regs() - Set initial values of GPIO pins + * @mm_gc: pointer to memory mapped GPIO chip structure + */ +static void ltq_mm_save_regs(struct of_mm_gpio_chip *mm_gc) +{ + struct ltq_mm *chip = + container_of(mm_gc, struct ltq_mm, mmchip); + + /* tell the ebu controller which memory address we will be using */ + ltq_ebu_w32(CPHYSADDR(chip->mmchip.regs) | 0x1, LTQ_EBU_ADDRSEL1); + + ltq_mm_apply(chip); +} + +static int ltq_mm_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct ltq_mm *chip; + const __be32 *shadow; + int ret = 0; + + if (!res) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + return -ENOENT; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->mmchip.gc.ngpio = 16; + chip->mmchip.gc.label = "gpio-mm-ltq"; + chip->mmchip.gc.direction_output = ltq_mm_dir_out; + chip->mmchip.gc.set = ltq_mm_set; + chip->mmchip.save_regs = ltq_mm_save_regs; + + /* store the shadow value if one was passed by the devicetree */ + shadow = of_get_property(pdev->dev.of_node, "lantiq,shadow", NULL); + if (shadow) + chip->shadow = be32_to_cpu(*shadow); + + ret = of_mm_gpiochip_add(pdev->dev.of_node, &chip->mmchip); + if (ret) + kfree(chip); + return ret; +} + +static const struct of_device_id ltq_mm_match[] = { + { .compatible = "lantiq,gpio-mm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_mm_match); + +static struct platform_driver ltq_mm_driver = { + .probe = ltq_mm_probe, + .driver = { + .name = "gpio-mm-ltq", + .owner = THIS_MODULE, + .of_match_table = ltq_mm_match, + }, +}; + +static int __init ltq_mm_init(void) +{ + return platform_driver_register(<q_mm_driver); +} + +subsys_initcall(ltq_mm_init); diff --git a/drivers/gpio/gpio-stp-xway.c b/drivers/gpio/gpio-stp-xway.c new file mode 100644 index 00000000000..e35096bf3cf --- /dev/null +++ b/drivers/gpio/gpio-stp-xway.c @@ -0,0 +1,301 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Copyright (C) 2012 John Crispin <blogic@openwrt.org> + * + */ + +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/of_platform.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <lantiq_soc.h> + +/* + * The Serial To Parallel (STP) is found on MIPS based Lantiq socs. It is a + * peripheral controller used to drive external shift register cascades. At most + * 3 groups of 8 bits can be driven. The hardware is able to allow the DSL modem + * to drive the 2 LSBs of the cascade automatically. + */ + +/* control register 0 */ +#define XWAY_STP_CON0 0x00 +/* control register 1 */ +#define XWAY_STP_CON1 0x04 +/* data register 0 */ +#define XWAY_STP_CPU0 0x08 +/* data register 1 */ +#define XWAY_STP_CPU1 0x0C +/* access register */ +#define XWAY_STP_AR 0x10 + +/* software or hardware update select bit */ +#define XWAY_STP_CON_SWU BIT(31) + +/* automatic update rates */ +#define XWAY_STP_2HZ 0 +#define XWAY_STP_4HZ BIT(23) +#define XWAY_STP_8HZ BIT(24) +#define XWAY_STP_10HZ (BIT(24) | BIT(23)) +#define XWAY_STP_SPEED_MASK (0xf << 23) + +/* clock source for automatic update */ +#define XWAY_STP_UPD_FPI BIT(31) +#define XWAY_STP_UPD_MASK (BIT(31) | BIT(30)) + +/* let the adsl core drive the 2 LSBs */ +#define XWAY_STP_ADSL_SHIFT 24 +#define XWAY_STP_ADSL_MASK 0x3 + +/* 2 groups of 3 bits can be driven by the phys */ +#define XWAY_STP_PHY_MASK 0x3 +#define XWAY_STP_PHY1_SHIFT 27 +#define XWAY_STP_PHY2_SHIFT 15 + +/* STP has 3 groups of 8 bits */ +#define XWAY_STP_GROUP0 BIT(0) +#define XWAY_STP_GROUP1 BIT(1) +#define XWAY_STP_GROUP2 BIT(2) +#define XWAY_STP_GROUP_MASK (0x7) + +/* Edge configuration bits */ +#define XWAY_STP_FALLING BIT(26) +#define XWAY_STP_EDGE_MASK BIT(26) + +#define xway_stp_r32(m, reg) __raw_readl(m + reg) +#define xway_stp_w32(m, val, reg) __raw_writel(val, m + reg) +#define xway_stp_w32_mask(m, clear, set, reg) \ + ltq_w32((ltq_r32(m + reg) & ~(clear)) | (set), \ + m + reg) + +struct xway_stp { + struct gpio_chip gc; + void __iomem *virt; + u32 edge; /* rising or falling edge triggered shift register */ + u16 shadow; /* shadow the shift registers state */ + u8 groups; /* we can drive 1-3 groups of 8bit each */ + u8 dsl; /* the 2 LSBs can be driven by the dsl core */ + u8 phy1; /* 3 bits can be driven by phy1 */ + u8 phy2; /* 3 bits can be driven by phy2 */ + u8 reserved; /* mask out the hw driven bits in gpio_request */ +}; + +/** + * xway_stp_set() - gpio_chip->set - set gpios. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * @val: Value to be written to specified signal. + * + * Set the shadow value and call ltq_ebu_apply. + */ +static void xway_stp_set(struct gpio_chip *gc, unsigned gpio, int val) +{ + struct xway_stp *chip = + container_of(gc, struct xway_stp, gc); + + if (val) + chip->shadow |= BIT(gpio); + else + chip->shadow &= ~BIT(gpio); + xway_stp_w32(chip->virt, chip->shadow, XWAY_STP_CPU0); + xway_stp_w32_mask(chip->virt, 0, XWAY_STP_CON_SWU, XWAY_STP_CON0); +} + +/** + * xway_stp_dir_out() - gpio_chip->dir_out - set gpio direction. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * @val: Value to be written to specified signal. + * + * Same as xway_stp_set, always returns 0. + */ +static int xway_stp_dir_out(struct gpio_chip *gc, unsigned gpio, int val) +{ + xway_stp_set(gc, gpio, val); + + return 0; +} + +/** + * xway_stp_request() - gpio_chip->request + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * + * We mask out the HW driven pins + */ +static int xway_stp_request(struct gpio_chip *gc, unsigned gpio) +{ + struct xway_stp *chip = + container_of(gc, struct xway_stp, gc); + + if ((gpio < 8) && (chip->reserved & BIT(gpio))) { + dev_err(gc->dev, "GPIO %d is driven by hardware\n", gpio); + return -ENODEV; + } + + return 0; +} + +/** + * xway_stp_hw_init() - Configure the STP unit and enable the clock gate + * @virt: pointer to the remapped register range + */ +static int xway_stp_hw_init(struct xway_stp *chip) +{ + /* sane defaults */ + xway_stp_w32(chip->virt, 0, XWAY_STP_AR); + xway_stp_w32(chip->virt, 0, XWAY_STP_CPU0); + xway_stp_w32(chip->virt, 0, XWAY_STP_CPU1); + xway_stp_w32(chip->virt, XWAY_STP_CON_SWU, XWAY_STP_CON0); + xway_stp_w32(chip->virt, 0, XWAY_STP_CON1); + + /* apply edge trigger settings for the shift register */ + xway_stp_w32_mask(chip->virt, XWAY_STP_EDGE_MASK, + chip->edge, XWAY_STP_CON0); + + /* apply led group settings */ + xway_stp_w32_mask(chip->virt, XWAY_STP_GROUP_MASK, + chip->groups, XWAY_STP_CON1); + + /* tell the hardware which pins are controlled by the dsl modem */ + xway_stp_w32_mask(chip->virt, + XWAY_STP_ADSL_MASK << XWAY_STP_ADSL_SHIFT, + chip->dsl << XWAY_STP_ADSL_SHIFT, + XWAY_STP_CON0); + + /* tell the hardware which pins are controlled by the phys */ + xway_stp_w32_mask(chip->virt, + XWAY_STP_PHY_MASK << XWAY_STP_PHY1_SHIFT, + chip->phy1 << XWAY_STP_PHY1_SHIFT, + XWAY_STP_CON0); + xway_stp_w32_mask(chip->virt, + XWAY_STP_PHY_MASK << XWAY_STP_PHY2_SHIFT, + chip->phy2 << XWAY_STP_PHY2_SHIFT, + XWAY_STP_CON1); + + /* mask out the hw driven bits in gpio_request */ + chip->reserved = (chip->phy2 << 5) | (chip->phy1 << 2) | chip->dsl; + + /* + * if we have pins that are driven by hw, we need to tell the stp what + * clock to use as a timer. + */ + if (chip->reserved) + xway_stp_w32_mask(chip->virt, XWAY_STP_UPD_MASK, + XWAY_STP_UPD_FPI, XWAY_STP_CON1); + + return 0; +} + +static int __devinit xway_stp_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + const __be32 *shadow, *groups, *dsl, *phy; + struct xway_stp *chip; + struct clk *clk; + int ret = 0; + + if (!res) { + dev_err(&pdev->dev, "failed to request STP resource\n"); + return -ENOENT; + } + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->virt = devm_request_and_ioremap(&pdev->dev, res); + if (!chip->virt) { + dev_err(&pdev->dev, "failed to remap STP memory\n"); + return -ENOMEM; + } + chip->gc.dev = &pdev->dev; + chip->gc.label = "stp-xway"; + chip->gc.direction_output = xway_stp_dir_out; + chip->gc.set = xway_stp_set; + chip->gc.request = xway_stp_request; + chip->gc.base = -1; + chip->gc.owner = THIS_MODULE; + + /* store the shadow value if one was passed by the devicetree */ + shadow = of_get_property(pdev->dev.of_node, "lantiq,shadow", NULL); + if (shadow) + chip->shadow = be32_to_cpu(*shadow); + + /* find out which gpio groups should be enabled */ + groups = of_get_property(pdev->dev.of_node, "lantiq,groups", NULL); + if (groups) + chip->groups = be32_to_cpu(*groups) & XWAY_STP_GROUP_MASK; + else + chip->groups = XWAY_STP_GROUP0; + chip->gc.ngpio = fls(chip->groups) * 8; + + /* find out which gpios are controlled by the dsl core */ + dsl = of_get_property(pdev->dev.of_node, "lantiq,dsl", NULL); + if (dsl) + chip->dsl = be32_to_cpu(*dsl) & XWAY_STP_ADSL_MASK; + + /* find out which gpios are controlled by the phys */ + if (of_machine_is_compatible("lantiq,ar9") || + of_machine_is_compatible("lantiq,gr9") || + of_machine_is_compatible("lantiq,vr9")) { + phy = of_get_property(pdev->dev.of_node, "lantiq,phy1", NULL); + if (phy) + chip->phy1 = be32_to_cpu(*phy) & XWAY_STP_PHY_MASK; + phy = of_get_property(pdev->dev.of_node, "lantiq,phy2", NULL); + if (phy) + chip->phy2 = be32_to_cpu(*phy) & XWAY_STP_PHY_MASK; + } + + /* check which edge trigger we should use, default to a falling edge */ + if (!of_find_property(pdev->dev.of_node, "lantiq,rising", NULL)) + chip->edge = XWAY_STP_FALLING; + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return PTR_ERR(clk); + } + clk_enable(clk); + + ret = xway_stp_hw_init(chip); + if (!ret) + ret = gpiochip_add(&chip->gc); + + if (!ret) + dev_info(&pdev->dev, "Init done\n"); + + return ret; +} + +static const struct of_device_id xway_stp_match[] = { + { .compatible = "lantiq,gpio-stp-xway" }, + {}, +}; +MODULE_DEVICE_TABLE(of, xway_stp_match); + +static struct platform_driver xway_stp_driver = { + .probe = xway_stp_probe, + .driver = { + .name = "gpio-stp-xway", + .owner = THIS_MODULE, + .of_match_table = xway_stp_match, + }, +}; + +int __init xway_stp_init(void) +{ + return platform_driver_register(&xway_stp_driver); +} + +subsys_initcall(xway_stp_init); diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 92cea9d77ec..08a7aa722d6 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2116,7 +2116,7 @@ out: return ret; } -static int format_check(struct drm_mode_fb_cmd2 *r) +static int format_check(const struct drm_mode_fb_cmd2 *r) { uint32_t format = r->pixel_format & ~DRM_FORMAT_BIG_ENDIAN; @@ -2185,7 +2185,7 @@ static int format_check(struct drm_mode_fb_cmd2 *r) } } -static int framebuffer_check(struct drm_mode_fb_cmd2 *r) +static int framebuffer_check(const struct drm_mode_fb_cmd2 *r) { int ret, hsub, vsub, num_planes, i; @@ -3126,7 +3126,7 @@ int drm_mode_connector_update_edid_property(struct drm_connector *connector, EXPORT_SYMBOL(drm_mode_connector_update_edid_property); static bool drm_property_change_is_valid(struct drm_property *property, - __u64 value) + uint64_t value) { if (property->flags & DRM_MODE_PROP_IMMUTABLE) return false; @@ -3136,7 +3136,7 @@ static bool drm_property_change_is_valid(struct drm_property *property, return true; } else if (property->flags & DRM_MODE_PROP_BITMASK) { int i; - __u64 valid_mask = 0; + uint64_t valid_mask = 0; for (i = 0; i < property->num_values; i++) valid_mask |= (1ULL << property->values[i]); return !(value & ~valid_mask); diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 608bddfc7e3..c3b5139eba7 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -66,6 +66,8 @@ #define EDID_QUIRK_FIRST_DETAILED_PREFERRED (1 << 5) /* use +hsync +vsync for detailed mode */ #define EDID_QUIRK_DETAILED_SYNC_PP (1 << 6) +/* Force reduced-blanking timings for detailed modes */ +#define EDID_QUIRK_FORCE_REDUCED_BLANKING (1 << 7) struct detailed_mode_closure { struct drm_connector *connector; @@ -120,6 +122,9 @@ static struct edid_quirk { /* Samsung SyncMaster 22[5-6]BW */ { "SAM", 596, EDID_QUIRK_PREFER_LARGE_60 }, { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 }, + + /* ViewSonic VA2026w */ + { "VSC", 5020, EDID_QUIRK_FORCE_REDUCED_BLANKING }, }; /*** DDC fetch and block validation ***/ @@ -885,12 +890,19 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, "Wrong Hsync/Vsync pulse width\n"); return NULL; } + + if (quirks & EDID_QUIRK_FORCE_REDUCED_BLANKING) { + mode = drm_cvt_mode(dev, hactive, vactive, 60, true, false, false); + if (!mode) + return NULL; + + goto set_size; + } + mode = drm_mode_create(dev); if (!mode) return NULL; - mode->type = DRM_MODE_TYPE_DRIVER; - if (quirks & EDID_QUIRK_135_CLOCK_TOO_HIGH) timing->pixel_clock = cpu_to_le16(1088); @@ -914,8 +926,6 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, drm_mode_do_interlace_quirk(mode, pt); - drm_mode_set_name(mode); - if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) { pt->misc |= DRM_EDID_PT_HSYNC_POSITIVE | DRM_EDID_PT_VSYNC_POSITIVE; } @@ -925,6 +935,7 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, mode->flags |= (pt->misc & DRM_EDID_PT_VSYNC_POSITIVE) ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC; +set_size: mode->width_mm = pt->width_mm_lo | (pt->width_height_mm_hi & 0xf0) << 4; mode->height_mm = pt->height_mm_lo | (pt->width_height_mm_hi & 0xf) << 8; @@ -938,6 +949,9 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, mode->height_mm = edid->height_cm * 10; } + mode->type = DRM_MODE_TYPE_DRIVER; + drm_mode_set_name(mode); + return mode; } diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index eb2b3c25b9e..5363e9c66c2 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -2032,6 +2032,8 @@ void i915_debugfs_cleanup(struct drm_minor *minor) 1, minor); drm_debugfs_remove_files((struct drm_info_list *) &i915_ring_stop_fops, 1, minor); + drm_debugfs_remove_files((struct drm_info_list *) &i915_error_state_fops, + 1, minor); } #endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 377c21f531e..c9cfc67c2cf 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -942,6 +942,9 @@ struct drm_i915_gem_object { /* prime dma-buf support */ struct sg_table *sg_table; + void *dma_buf_vmapping; + int vmapping_count; + /** * Used for performing relocations during execbuffer insertion. */ diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index c1e5c66553d..288d7b8f49a 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -2063,10 +2063,8 @@ i915_gem_object_unbind(struct drm_i915_gem_object *obj) if (obj->gtt_space == NULL) return 0; - if (obj->pin_count != 0) { - DRM_ERROR("Attempting to unbind pinned buffer\n"); - return -EINVAL; - } + if (obj->pin_count) + return -EBUSY; ret = i915_gem_object_finish_gpu(obj); if (ret) @@ -3293,6 +3291,7 @@ struct drm_i915_gem_object *i915_gem_alloc_object(struct drm_device *dev, struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_gem_object *obj; struct address_space *mapping; + u32 mask; obj = kzalloc(sizeof(*obj), GFP_KERNEL); if (obj == NULL) @@ -3303,8 +3302,15 @@ struct drm_i915_gem_object *i915_gem_alloc_object(struct drm_device *dev, return NULL; } + mask = GFP_HIGHUSER | __GFP_RECLAIMABLE; + if (IS_CRESTLINE(dev) || IS_BROADWATER(dev)) { + /* 965gm cannot relocate objects above 4GiB. */ + mask &= ~__GFP_HIGHMEM; + mask |= __GFP_DMA32; + } + mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; - mapping_set_gfp_mask(mapping, GFP_HIGHUSER | __GFP_RECLAIMABLE); + mapping_set_gfp_mask(mapping, mask); i915_gem_info_add_obj(dev_priv, size); diff --git a/drivers/gpu/drm/i915/i915_gem_dmabuf.c b/drivers/gpu/drm/i915/i915_gem_dmabuf.c index 8e269178d6a..aa308e1337d 100644 --- a/drivers/gpu/drm/i915/i915_gem_dmabuf.c +++ b/drivers/gpu/drm/i915/i915_gem_dmabuf.c @@ -74,6 +74,59 @@ static void i915_gem_dmabuf_release(struct dma_buf *dma_buf) } } +static void *i915_gem_dmabuf_vmap(struct dma_buf *dma_buf) +{ + struct drm_i915_gem_object *obj = dma_buf->priv; + struct drm_device *dev = obj->base.dev; + int ret; + + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return ERR_PTR(ret); + + if (obj->dma_buf_vmapping) { + obj->vmapping_count++; + goto out_unlock; + } + + if (!obj->pages) { + ret = i915_gem_object_get_pages_gtt(obj, __GFP_NORETRY | __GFP_NOWARN); + if (ret) { + mutex_unlock(&dev->struct_mutex); + return ERR_PTR(ret); + } + } + + obj->dma_buf_vmapping = vmap(obj->pages, obj->base.size / PAGE_SIZE, 0, PAGE_KERNEL); + if (!obj->dma_buf_vmapping) { + DRM_ERROR("failed to vmap object\n"); + goto out_unlock; + } + + obj->vmapping_count = 1; +out_unlock: + mutex_unlock(&dev->struct_mutex); + return obj->dma_buf_vmapping; +} + +static void i915_gem_dmabuf_vunmap(struct dma_buf *dma_buf, void *vaddr) +{ + struct drm_i915_gem_object *obj = dma_buf->priv; + struct drm_device *dev = obj->base.dev; + int ret; + + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return; + + --obj->vmapping_count; + if (obj->vmapping_count == 0) { + vunmap(obj->dma_buf_vmapping); + obj->dma_buf_vmapping = NULL; + } + mutex_unlock(&dev->struct_mutex); +} + static void *i915_gem_dmabuf_kmap_atomic(struct dma_buf *dma_buf, unsigned long page_num) { return NULL; @@ -93,6 +146,11 @@ static void i915_gem_dmabuf_kunmap(struct dma_buf *dma_buf, unsigned long page_n } +static int i915_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) +{ + return -EINVAL; +} + static const struct dma_buf_ops i915_dmabuf_ops = { .map_dma_buf = i915_gem_map_dma_buf, .unmap_dma_buf = i915_gem_unmap_dma_buf, @@ -101,6 +159,9 @@ static const struct dma_buf_ops i915_dmabuf_ops = { .kmap_atomic = i915_gem_dmabuf_kmap_atomic, .kunmap = i915_gem_dmabuf_kunmap, .kunmap_atomic = i915_gem_dmabuf_kunmap_atomic, + .mmap = i915_gem_dmabuf_mmap, + .vmap = i915_gem_dmabuf_vmap, + .vunmap = i915_gem_dmabuf_vunmap, }; struct dma_buf *i915_gem_prime_export(struct drm_device *dev, diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index cc4a6330761..1417660a93e 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -350,8 +350,8 @@ static void gen6_pm_rps_work(struct work_struct *work) { drm_i915_private_t *dev_priv = container_of(work, drm_i915_private_t, rps_work); - u8 new_delay = dev_priv->cur_delay; u32 pm_iir, pm_imr; + u8 new_delay; spin_lock_irq(&dev_priv->rps_lock); pm_iir = dev_priv->pm_iir; @@ -360,41 +360,18 @@ static void gen6_pm_rps_work(struct work_struct *work) I915_WRITE(GEN6_PMIMR, 0); spin_unlock_irq(&dev_priv->rps_lock); - if (!pm_iir) + if ((pm_iir & GEN6_PM_DEFERRED_EVENTS) == 0) return; mutex_lock(&dev_priv->dev->struct_mutex); - if (pm_iir & GEN6_PM_RP_UP_THRESHOLD) { - if (dev_priv->cur_delay != dev_priv->max_delay) - new_delay = dev_priv->cur_delay + 1; - if (new_delay > dev_priv->max_delay) - new_delay = dev_priv->max_delay; - } else if (pm_iir & (GEN6_PM_RP_DOWN_THRESHOLD | GEN6_PM_RP_DOWN_TIMEOUT)) { - gen6_gt_force_wake_get(dev_priv); - if (dev_priv->cur_delay != dev_priv->min_delay) - new_delay = dev_priv->cur_delay - 1; - if (new_delay < dev_priv->min_delay) { - new_delay = dev_priv->min_delay; - I915_WRITE(GEN6_RP_INTERRUPT_LIMITS, - I915_READ(GEN6_RP_INTERRUPT_LIMITS) | - ((new_delay << 16) & 0x3f0000)); - } else { - /* Make sure we continue to get down interrupts - * until we hit the minimum frequency */ - I915_WRITE(GEN6_RP_INTERRUPT_LIMITS, - I915_READ(GEN6_RP_INTERRUPT_LIMITS) & ~0x3f0000); - } - gen6_gt_force_wake_put(dev_priv); - } + + if (pm_iir & GEN6_PM_RP_UP_THRESHOLD) + new_delay = dev_priv->cur_delay + 1; + else + new_delay = dev_priv->cur_delay - 1; gen6_set_rps(dev_priv->dev, new_delay); - dev_priv->cur_delay = new_delay; - /* - * rps_lock not held here because clearing is non-destructive. There is - * an *extremely* unlikely race with gen6_rps_enable() that is prevented - * by holding struct_mutex for the duration of the write. - */ mutex_unlock(&dev_priv->dev->struct_mutex); } diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index ee61ad1e642..91478942090 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -910,9 +910,10 @@ static void assert_pll(struct drm_i915_private *dev_priv, /* For ILK+ */ static void assert_pch_pll(struct drm_i915_private *dev_priv, - struct intel_crtc *intel_crtc, bool state) + struct intel_pch_pll *pll, + struct intel_crtc *crtc, + bool state) { - int reg; u32 val; bool cur_state; @@ -921,30 +922,37 @@ static void assert_pch_pll(struct drm_i915_private *dev_priv, return; } - if (!intel_crtc->pch_pll) { - WARN(1, "asserting PCH PLL enabled with no PLL\n"); + if (WARN (!pll, + "asserting PCH PLL %s with no PLL\n", state_string(state))) return; - } - if (HAS_PCH_CPT(dev_priv->dev)) { + val = I915_READ(pll->pll_reg); + cur_state = !!(val & DPLL_VCO_ENABLE); + WARN(cur_state != state, + "PCH PLL state for reg %x assertion failure (expected %s, current %s), val=%08x\n", + pll->pll_reg, state_string(state), state_string(cur_state), val); + + /* Make sure the selected PLL is correctly attached to the transcoder */ + if (crtc && HAS_PCH_CPT(dev_priv->dev)) { u32 pch_dpll; pch_dpll = I915_READ(PCH_DPLL_SEL); - - /* Make sure the selected PLL is enabled to the transcoder */ - WARN(!((pch_dpll >> (4 * intel_crtc->pipe)) & 8), - "transcoder %d PLL not enabled\n", intel_crtc->pipe); + cur_state = pll->pll_reg == _PCH_DPLL_B; + if (!WARN(((pch_dpll >> (4 * crtc->pipe)) & 1) != cur_state, + "PLL[%d] not attached to this transcoder %d: %08x\n", + cur_state, crtc->pipe, pch_dpll)) { + cur_state = !!(val >> (4*crtc->pipe + 3)); + WARN(cur_state != state, + "PLL[%d] not %s on this transcoder %d: %08x\n", + pll->pll_reg == _PCH_DPLL_B, + state_string(state), + crtc->pipe, + val); + } } - - reg = intel_crtc->pch_pll->pll_reg; - val = I915_READ(reg); - cur_state = !!(val & DPLL_VCO_ENABLE); - WARN(cur_state != state, - "PCH PLL state assertion failure (expected %s, current %s)\n", - state_string(state), state_string(cur_state)); } -#define assert_pch_pll_enabled(d, p) assert_pch_pll(d, p, true) -#define assert_pch_pll_disabled(d, p) assert_pch_pll(d, p, false) +#define assert_pch_pll_enabled(d, p, c) assert_pch_pll(d, p, c, true) +#define assert_pch_pll_disabled(d, p, c) assert_pch_pll(d, p, c, false) static void assert_fdi_tx(struct drm_i915_private *dev_priv, enum pipe pipe, bool state) @@ -1424,7 +1432,7 @@ static void intel_enable_pch_pll(struct intel_crtc *intel_crtc) assert_pch_refclk_enabled(dev_priv); if (pll->active++ && pll->on) { - assert_pch_pll_enabled(dev_priv, intel_crtc); + assert_pch_pll_enabled(dev_priv, pll, NULL); return; } @@ -1460,12 +1468,12 @@ static void intel_disable_pch_pll(struct intel_crtc *intel_crtc) intel_crtc->base.base.id); if (WARN_ON(pll->active == 0)) { - assert_pch_pll_disabled(dev_priv, intel_crtc); + assert_pch_pll_disabled(dev_priv, pll, NULL); return; } if (--pll->active) { - assert_pch_pll_enabled(dev_priv, intel_crtc); + assert_pch_pll_enabled(dev_priv, pll, NULL); return; } @@ -1495,7 +1503,9 @@ static void intel_enable_transcoder(struct drm_i915_private *dev_priv, BUG_ON(dev_priv->info->gen < 5); /* Make sure PCH DPLL is enabled */ - assert_pch_pll_enabled(dev_priv, to_intel_crtc(crtc)); + assert_pch_pll_enabled(dev_priv, + to_intel_crtc(crtc)->pch_pll, + to_intel_crtc(crtc)); /* FDI must be feeding us bits for PCH ports */ assert_fdi_tx_enabled(dev_priv, pipe); diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 71c7096e386..296cfc201a8 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -266,6 +266,9 @@ intel_dp_mode_valid(struct drm_connector *connector, if (mode->clock < 10000) return MODE_CLOCK_LOW; + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return MODE_H_ILLEGAL; + return MODE_OK; } @@ -702,6 +705,9 @@ intel_dp_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode, mode->clock = intel_dp->panel_fixed_mode->clock; } + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return false; + DRM_DEBUG_KMS("DP link computation with max lane count %i " "max bw %02x pixel clock %iKHz\n", max_lane_count, bws[max_clock], mode->clock); @@ -1154,11 +1160,10 @@ static void ironlake_edp_panel_off(struct intel_dp *intel_dp) DRM_DEBUG_KMS("Turn eDP power off\n"); - WARN(intel_dp->want_panel_vdd, "Cannot turn power off while VDD is on\n"); - ironlake_panel_vdd_off_sync(intel_dp); /* finish any pending work */ + WARN(!intel_dp->want_panel_vdd, "Need VDD to turn off panel\n"); pp = ironlake_get_pp_control(dev_priv); - pp &= ~(POWER_TARGET_ON | EDP_FORCE_VDD | PANEL_POWER_RESET | EDP_BLC_ENABLE); + pp &= ~(POWER_TARGET_ON | PANEL_POWER_RESET | EDP_BLC_ENABLE); I915_WRITE(PCH_PP_CONTROL, pp); POSTING_READ(PCH_PP_CONTROL); @@ -1266,18 +1271,16 @@ static void intel_dp_prepare(struct drm_encoder *encoder) { struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + /* Make sure the panel is off before trying to change the mode. But also + * ensure that we have vdd while we switch off the panel. */ + ironlake_edp_panel_vdd_on(intel_dp); ironlake_edp_backlight_off(intel_dp); ironlake_edp_panel_off(intel_dp); - /* Wake up the sink first */ - ironlake_edp_panel_vdd_on(intel_dp); intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON); intel_dp_link_down(intel_dp); ironlake_edp_panel_vdd_off(intel_dp, false); - - /* Make sure the panel is off before trying to - * change the mode - */ } static void intel_dp_commit(struct drm_encoder *encoder) @@ -1309,10 +1312,11 @@ intel_dp_dpms(struct drm_encoder *encoder, int mode) uint32_t dp_reg = I915_READ(intel_dp->output_reg); if (mode != DRM_MODE_DPMS_ON) { + /* Switching the panel off requires vdd. */ + ironlake_edp_panel_vdd_on(intel_dp); ironlake_edp_backlight_off(intel_dp); ironlake_edp_panel_off(intel_dp); - ironlake_edp_panel_vdd_on(intel_dp); intel_dp_sink_dpms(intel_dp, mode); intel_dp_link_down(intel_dp); ironlake_edp_panel_vdd_off(intel_dp, false); diff --git a/drivers/gpu/drm/i915/intel_i2c.c b/drivers/gpu/drm/i915/intel_i2c.c index 4a9707dd0f9..1991a4408cf 100644 --- a/drivers/gpu/drm/i915/intel_i2c.c +++ b/drivers/gpu/drm/i915/intel_i2c.c @@ -396,11 +396,22 @@ clear_err: * Wait for bus to IDLE before clearing NAK. * If we clear the NAK while bus is still active, then it will stay * active and the next transaction may fail. + * + * If no ACK is received during the address phase of a transaction, the + * adapter must report -ENXIO. It is not clear what to return if no ACK + * is received at other times. But we have to be careful to not return + * spurious -ENXIO because that will prevent i2c and drm edid functions + * from retrying. So return -ENXIO only when gmbus properly quiescents - + * timing out seems to happen when there _is_ a ddc chip present, but + * it's slow responding and only answers on the 2nd retry. */ + ret = -ENXIO; if (wait_for((I915_READ(GMBUS2 + reg_offset) & GMBUS_ACTIVE) == 0, - 10)) + 10)) { DRM_DEBUG_KMS("GMBUS [%s] timed out after NAK\n", adapter->name); + ret = -ETIMEDOUT; + } /* Toggle the Software Clear Interrupt bit. This has the effect * of resetting the GMBUS controller and so clearing the @@ -414,14 +425,6 @@ clear_err: adapter->name, msgs[i].addr, (msgs[i].flags & I2C_M_RD) ? 'r' : 'w', msgs[i].len); - /* - * If no ACK is received during the address phase of a transaction, - * the adapter must report -ENXIO. - * It is not clear what to return if no ACK is received at other times. - * So, we always return -ENXIO in all NAK cases, to ensure we send - * it at least during the one case that is specified. - */ - ret = -ENXIO; goto out; timeout: diff --git a/drivers/gpu/drm/i915/intel_lvds.c b/drivers/gpu/drm/i915/intel_lvds.c index 9dee82350de..08eb04c787e 100644 --- a/drivers/gpu/drm/i915/intel_lvds.c +++ b/drivers/gpu/drm/i915/intel_lvds.c @@ -747,6 +747,14 @@ static const struct dmi_system_id intel_no_lvds[] = { }, { .callback = intel_no_lvds_dmi_callback, + .ident = "Hewlett-Packard HP t5740e Thin Client", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP t5740e Thin Client"), + }, + }, + { + .callback = intel_no_lvds_dmi_callback, .ident = "Hewlett-Packard t5745", .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c index 8e79ff67ec9..d0ce2a5b1d3 100644 --- a/drivers/gpu/drm/i915/intel_pm.c +++ b/drivers/gpu/drm/i915/intel_pm.c @@ -2270,10 +2270,33 @@ void ironlake_disable_drps(struct drm_device *dev) void gen6_set_rps(struct drm_device *dev, u8 val) { struct drm_i915_private *dev_priv = dev->dev_private; - u32 swreq; + u32 limits; - swreq = (val & 0x3ff) << 25; - I915_WRITE(GEN6_RPNSWREQ, swreq); + limits = 0; + if (val >= dev_priv->max_delay) + val = dev_priv->max_delay; + else + limits |= dev_priv->max_delay << 24; + + if (val <= dev_priv->min_delay) + val = dev_priv->min_delay; + else + limits |= dev_priv->min_delay << 16; + + if (val == dev_priv->cur_delay) + return; + + I915_WRITE(GEN6_RPNSWREQ, + GEN6_FREQUENCY(val) | + GEN6_OFFSET(0) | + GEN6_AGGRESSIVE_TURBO); + + /* Make sure we continue to get interrupts + * until we hit the minimum or maximum frequencies. + */ + I915_WRITE(GEN6_RP_INTERRUPT_LIMITS, limits); + + dev_priv->cur_delay = val; } void gen6_disable_rps(struct drm_device *dev) @@ -2327,11 +2350,10 @@ int intel_enable_rc6(const struct drm_device *dev) void gen6_enable_rps(struct drm_i915_private *dev_priv) { struct intel_ring_buffer *ring; - u32 rp_state_cap = I915_READ(GEN6_RP_STATE_CAP); - u32 gt_perf_status = I915_READ(GEN6_GT_PERF_STATUS); + u32 rp_state_cap; + u32 gt_perf_status; u32 pcu_mbox, rc6_mask = 0; u32 gtfifodbg; - int cur_freq, min_freq, max_freq; int rc6_mode; int i; @@ -2352,6 +2374,14 @@ void gen6_enable_rps(struct drm_i915_private *dev_priv) gen6_gt_force_wake_get(dev_priv); + rp_state_cap = I915_READ(GEN6_RP_STATE_CAP); + gt_perf_status = I915_READ(GEN6_GT_PERF_STATUS); + + /* In units of 100MHz */ + dev_priv->max_delay = rp_state_cap & 0xff; + dev_priv->min_delay = (rp_state_cap & 0xff0000) >> 16; + dev_priv->cur_delay = 0; + /* disable the counters and set deterministic thresholds */ I915_WRITE(GEN6_RC_CONTROL, 0); @@ -2399,8 +2429,8 @@ void gen6_enable_rps(struct drm_i915_private *dev_priv) I915_WRITE(GEN6_RP_DOWN_TIMEOUT, 1000000); I915_WRITE(GEN6_RP_INTERRUPT_LIMITS, - 18 << 24 | - 6 << 16); + dev_priv->max_delay << 24 | + dev_priv->min_delay << 16); I915_WRITE(GEN6_RP_UP_THRESHOLD, 10000); I915_WRITE(GEN6_RP_DOWN_THRESHOLD, 1000000); I915_WRITE(GEN6_RP_UP_EI, 100000); @@ -2408,7 +2438,7 @@ void gen6_enable_rps(struct drm_i915_private *dev_priv) I915_WRITE(GEN6_RP_IDLE_HYSTERSIS, 10); I915_WRITE(GEN6_RP_CONTROL, GEN6_RP_MEDIA_TURBO | - GEN6_RP_MEDIA_HW_MODE | + GEN6_RP_MEDIA_HW_NORMAL_MODE | GEN6_RP_MEDIA_IS_GFX | GEN6_RP_ENABLE | GEN6_RP_UP_BUSY_AVG | @@ -2426,10 +2456,6 @@ void gen6_enable_rps(struct drm_i915_private *dev_priv) 500)) DRM_ERROR("timeout waiting for pcode mailbox to finish\n"); - min_freq = (rp_state_cap & 0xff0000) >> 16; - max_freq = rp_state_cap & 0xff; - cur_freq = (gt_perf_status & 0xff00) >> 8; - /* Check for overclock support */ if (wait_for((I915_READ(GEN6_PCODE_MAILBOX) & GEN6_PCODE_READY) == 0, 500)) @@ -2440,14 +2466,11 @@ void gen6_enable_rps(struct drm_i915_private *dev_priv) 500)) DRM_ERROR("timeout waiting for pcode mailbox to finish\n"); if (pcu_mbox & (1<<31)) { /* OC supported */ - max_freq = pcu_mbox & 0xff; + dev_priv->max_delay = pcu_mbox & 0xff; DRM_DEBUG_DRIVER("overclocking supported, adjusting frequency max to %dMHz\n", pcu_mbox * 50); } - /* In units of 100MHz */ - dev_priv->max_delay = max_freq; - dev_priv->min_delay = min_freq; - dev_priv->cur_delay = cur_freq; + gen6_set_rps(dev_priv->dev, (gt_perf_status & 0xff00) >> 8); /* requires MSI enabled */ I915_WRITE(GEN6_PMIER, @@ -3580,8 +3603,9 @@ static void gen6_sanitize_pm(struct drm_device *dev) limits |= (dev_priv->min_delay & 0x3f) << 16; if (old != limits) { - DRM_ERROR("Power management discrepancy: GEN6_RP_INTERRUPT_LIMITS expected %08x, was %08x\n", - limits, old); + /* Note that the known failure case is to read back 0. */ + DRM_DEBUG_DRIVER("Power management discrepancy: GEN6_RP_INTERRUPT_LIMITS " + "expected %08x, was %08x\n", limits, old); I915_WRITE(GEN6_RP_INTERRUPT_LIMITS, limits); } diff --git a/drivers/gpu/drm/i915/intel_sdvo.c b/drivers/gpu/drm/i915/intel_sdvo.c index a949b73880c..b6a9d45fc3c 100644 --- a/drivers/gpu/drm/i915/intel_sdvo.c +++ b/drivers/gpu/drm/i915/intel_sdvo.c @@ -783,10 +783,12 @@ static void intel_sdvo_get_dtd_from_mode(struct intel_sdvo_dtd *dtd, ((v_sync_len & 0x30) >> 4); dtd->part2.dtd_flags = 0x18; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + dtd->part2.dtd_flags |= DTD_FLAG_INTERLACE; if (mode->flags & DRM_MODE_FLAG_PHSYNC) - dtd->part2.dtd_flags |= 0x2; + dtd->part2.dtd_flags |= DTD_FLAG_HSYNC_POSITIVE; if (mode->flags & DRM_MODE_FLAG_PVSYNC) - dtd->part2.dtd_flags |= 0x4; + dtd->part2.dtd_flags |= DTD_FLAG_VSYNC_POSITIVE; dtd->part2.sdvo_flags = 0; dtd->part2.v_sync_off_high = v_sync_offset & 0xc0; @@ -820,9 +822,11 @@ static void intel_sdvo_get_mode_from_dtd(struct drm_display_mode * mode, mode->clock = dtd->part1.clock * 10; mode->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); - if (dtd->part2.dtd_flags & 0x2) + if (dtd->part2.dtd_flags & DTD_FLAG_INTERLACE) + mode->flags |= DRM_MODE_FLAG_INTERLACE; + if (dtd->part2.dtd_flags & DTD_FLAG_HSYNC_POSITIVE) mode->flags |= DRM_MODE_FLAG_PHSYNC; - if (dtd->part2.dtd_flags & 0x4) + if (dtd->part2.dtd_flags & DTD_FLAG_VSYNC_POSITIVE) mode->flags |= DRM_MODE_FLAG_PVSYNC; } diff --git a/drivers/gpu/drm/i915/intel_sdvo_regs.h b/drivers/gpu/drm/i915/intel_sdvo_regs.h index 6b7b22f4d63..9d030142ee4 100644 --- a/drivers/gpu/drm/i915/intel_sdvo_regs.h +++ b/drivers/gpu/drm/i915/intel_sdvo_regs.h @@ -61,6 +61,11 @@ struct intel_sdvo_caps { u16 output_flags; } __attribute__((packed)); +/* Note: SDVO detailed timing flags match EDID misc flags. */ +#define DTD_FLAG_HSYNC_POSITIVE (1 << 1) +#define DTD_FLAG_VSYNC_POSITIVE (1 << 2) +#define DTD_FLAG_INTERLACE (1 << 7) + /** This matches the EDID DTD structure, more or less */ struct intel_sdvo_dtd { struct { diff --git a/drivers/gpu/drm/i915/intel_tv.c b/drivers/gpu/drm/i915/intel_tv.c index 3346612d295..a233a51fd7e 100644 --- a/drivers/gpu/drm/i915/intel_tv.c +++ b/drivers/gpu/drm/i915/intel_tv.c @@ -674,6 +674,54 @@ static const struct tv_mode tv_modes[] = { .filter_table = filter_table, }, { + .name = "480p", + .clock = 107520, + .refresh = 59940, + .oversample = TV_OVERSAMPLE_4X, + .component_only = 1, + + .hsync_end = 64, .hblank_end = 122, + .hblank_start = 842, .htotal = 857, + + .progressive = true, .trilevel_sync = false, + + .vsync_start_f1 = 12, .vsync_start_f2 = 12, + .vsync_len = 12, + + .veq_ena = false, + + .vi_end_f1 = 44, .vi_end_f2 = 44, + .nbr_end = 479, + + .burst_ena = false, + + .filter_table = filter_table, + }, + { + .name = "576p", + .clock = 107520, + .refresh = 50000, + .oversample = TV_OVERSAMPLE_4X, + .component_only = 1, + + .hsync_end = 64, .hblank_end = 139, + .hblank_start = 859, .htotal = 863, + + .progressive = true, .trilevel_sync = false, + + .vsync_start_f1 = 10, .vsync_start_f2 = 10, + .vsync_len = 10, + + .veq_ena = false, + + .vi_end_f1 = 48, .vi_end_f2 = 48, + .nbr_end = 575, + + .burst_ena = false, + + .filter_table = filter_table, + }, + { .name = "720p@60Hz", .clock = 148800, .refresh = 60000, @@ -1194,6 +1242,11 @@ intel_tv_detect_type(struct intel_tv *intel_tv, I915_WRITE(TV_DAC, save_tv_dac & ~TVDAC_STATE_CHG_EN); I915_WRITE(TV_CTL, save_tv_ctl); + POSTING_READ(TV_CTL); + + /* For unknown reasons the hw barfs if we don't do this vblank wait. */ + intel_wait_for_vblank(intel_tv->base.base.dev, + to_intel_crtc(intel_tv->base.base.crtc)->pipe); /* Restore interrupt config */ if (connector->polled & DRM_CONNECTOR_POLL_HPD) { diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index 634d222c93d..8613cb23808 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -123,6 +123,9 @@ struct nouveau_bo { struct drm_gem_object *gem; int pin_refcnt; + + struct ttm_bo_kmap_obj dma_buf_vmap; + int vmapping_count; }; #define nouveau_bo_tile_layout(nvbo) \ diff --git a/drivers/gpu/drm/nouveau/nouveau_prime.c b/drivers/gpu/drm/nouveau/nouveau_prime.c index c58aab7370c..a89240e5fb2 100644 --- a/drivers/gpu/drm/nouveau/nouveau_prime.c +++ b/drivers/gpu/drm/nouveau/nouveau_prime.c @@ -61,6 +61,48 @@ static void nouveau_gem_kunmap(struct dma_buf *dma_buf, unsigned long page_num, } +static int nouveau_gem_prime_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) +{ + return -EINVAL; +} + +static void *nouveau_gem_prime_vmap(struct dma_buf *dma_buf) +{ + struct nouveau_bo *nvbo = dma_buf->priv; + struct drm_device *dev = nvbo->gem->dev; + int ret; + + mutex_lock(&dev->struct_mutex); + if (nvbo->vmapping_count) { + nvbo->vmapping_count++; + goto out_unlock; + } + + ret = ttm_bo_kmap(&nvbo->bo, 0, nvbo->bo.num_pages, + &nvbo->dma_buf_vmap); + if (ret) { + mutex_unlock(&dev->struct_mutex); + return ERR_PTR(ret); + } + nvbo->vmapping_count = 1; +out_unlock: + mutex_unlock(&dev->struct_mutex); + return nvbo->dma_buf_vmap.virtual; +} + +static void nouveau_gem_prime_vunmap(struct dma_buf *dma_buf, void *vaddr) +{ + struct nouveau_bo *nvbo = dma_buf->priv; + struct drm_device *dev = nvbo->gem->dev; + + mutex_lock(&dev->struct_mutex); + nvbo->vmapping_count--; + if (nvbo->vmapping_count == 0) { + ttm_bo_kunmap(&nvbo->dma_buf_vmap); + } + mutex_unlock(&dev->struct_mutex); +} + static const struct dma_buf_ops nouveau_dmabuf_ops = { .map_dma_buf = nouveau_gem_map_dma_buf, .unmap_dma_buf = nouveau_gem_unmap_dma_buf, @@ -69,6 +111,9 @@ static const struct dma_buf_ops nouveau_dmabuf_ops = { .kmap_atomic = nouveau_gem_kmap_atomic, .kunmap = nouveau_gem_kunmap, .kunmap_atomic = nouveau_gem_kunmap_atomic, + .mmap = nouveau_gem_prime_mmap, + .vmap = nouveau_gem_prime_vmap, + .vunmap = nouveau_gem_prime_vunmap, }; static int diff --git a/drivers/gpu/drm/radeon/ni.c b/drivers/gpu/drm/radeon/ni.c index b01c2dd627b..ce4e7cc6c90 100644 --- a/drivers/gpu/drm/radeon/ni.c +++ b/drivers/gpu/drm/radeon/ni.c @@ -865,7 +865,7 @@ static void cayman_gpu_init(struct radeon_device *rdev) /* num banks is 8 on all fusion asics. 0 = 4, 1 = 8, 2 = 16 */ if (rdev->flags & RADEON_IS_IGP) - rdev->config.evergreen.tile_config |= 1 << 4; + rdev->config.cayman.tile_config |= 1 << 4; else rdev->config.cayman.tile_config |= ((mc_arb_ramcfg & NOOFBANK_MASK) >> NOOFBANK_SHIFT) << 4; diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index 1dc3a4aba02..2e24022b389 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h @@ -346,6 +346,9 @@ struct radeon_bo { /* Constant after initialization */ struct radeon_device *rdev; struct drm_gem_object gem_base; + + struct ttm_bo_kmap_obj dma_buf_vmap; + int vmapping_count; }; #define gem_to_radeon_bo(gobj) container_of((gobj), struct radeon_bo, gem_base) @@ -848,7 +851,6 @@ struct radeon_cs_parser { s32 priority; }; -extern int radeon_cs_update_pages(struct radeon_cs_parser *p, int pg_idx); extern int radeon_cs_finish_pages(struct radeon_cs_parser *p); extern u32 radeon_get_ib_value(struct radeon_cs_parser *p, int idx); diff --git a/drivers/gpu/drm/radeon/radeon_atombios.c b/drivers/gpu/drm/radeon/radeon_atombios.c index f6e69b8c06c..b1e3820df36 100644 --- a/drivers/gpu/drm/radeon/radeon_atombios.c +++ b/drivers/gpu/drm/radeon/radeon_atombios.c @@ -444,7 +444,9 @@ static bool radeon_atom_apply_quirks(struct drm_device *dev, */ if ((dev->pdev->device == 0x9498) && (dev->pdev->subsystem_vendor == 0x1682) && - (dev->pdev->subsystem_device == 0x2452)) { + (dev->pdev->subsystem_device == 0x2452) && + (i2c_bus->valid == false) && + !(supported_device & (ATOM_DEVICE_TV_SUPPORT | ATOM_DEVICE_CV_SUPPORT))) { struct radeon_device *rdev = dev->dev_private; *i2c_bus = radeon_lookup_i2c_gpio(rdev, 0x93); } diff --git a/drivers/gpu/drm/radeon/radeon_cs.c b/drivers/gpu/drm/radeon/radeon_cs.c index c7d64a73903..0137689ed46 100644 --- a/drivers/gpu/drm/radeon/radeon_cs.c +++ b/drivers/gpu/drm/radeon/radeon_cs.c @@ -580,7 +580,7 @@ int radeon_cs_finish_pages(struct radeon_cs_parser *p) return 0; } -int radeon_cs_update_pages(struct radeon_cs_parser *p, int pg_idx) +static int radeon_cs_update_pages(struct radeon_cs_parser *p, int pg_idx) { int new_page; struct radeon_cs_chunk *ibc = &p->chunks[p->chunk_ib_idx]; @@ -623,3 +623,28 @@ int radeon_cs_update_pages(struct radeon_cs_parser *p, int pg_idx) return new_page; } + +u32 radeon_get_ib_value(struct radeon_cs_parser *p, int idx) +{ + struct radeon_cs_chunk *ibc = &p->chunks[p->chunk_ib_idx]; + u32 pg_idx, pg_offset; + u32 idx_value = 0; + int new_page; + + pg_idx = (idx * 4) / PAGE_SIZE; + pg_offset = (idx * 4) % PAGE_SIZE; + + if (ibc->kpage_idx[0] == pg_idx) + return ibc->kpage[0][pg_offset/4]; + if (ibc->kpage_idx[1] == pg_idx) + return ibc->kpage[1][pg_offset/4]; + + new_page = radeon_cs_update_pages(p, pg_idx); + if (new_page < 0) { + p->parser_error = new_page; + return 0; + } + + idx_value = ibc->kpage[new_page][pg_offset/4]; + return idx_value; +} diff --git a/drivers/gpu/drm/radeon/radeon_prime.c b/drivers/gpu/drm/radeon/radeon_prime.c index b8f835d8ecb..8ddab4c7671 100644 --- a/drivers/gpu/drm/radeon/radeon_prime.c +++ b/drivers/gpu/drm/radeon/radeon_prime.c @@ -85,6 +85,47 @@ static void radeon_gem_kunmap(struct dma_buf *dma_buf, unsigned long page_num, v } +static int radeon_gem_prime_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) +{ + return -EINVAL; +} + +static void *radeon_gem_prime_vmap(struct dma_buf *dma_buf) +{ + struct radeon_bo *bo = dma_buf->priv; + struct drm_device *dev = bo->rdev->ddev; + int ret; + + mutex_lock(&dev->struct_mutex); + if (bo->vmapping_count) { + bo->vmapping_count++; + goto out_unlock; + } + + ret = ttm_bo_kmap(&bo->tbo, 0, bo->tbo.num_pages, + &bo->dma_buf_vmap); + if (ret) { + mutex_unlock(&dev->struct_mutex); + return ERR_PTR(ret); + } + bo->vmapping_count = 1; +out_unlock: + mutex_unlock(&dev->struct_mutex); + return bo->dma_buf_vmap.virtual; +} + +static void radeon_gem_prime_vunmap(struct dma_buf *dma_buf, void *vaddr) +{ + struct radeon_bo *bo = dma_buf->priv; + struct drm_device *dev = bo->rdev->ddev; + + mutex_lock(&dev->struct_mutex); + bo->vmapping_count--; + if (bo->vmapping_count == 0) { + ttm_bo_kunmap(&bo->dma_buf_vmap); + } + mutex_unlock(&dev->struct_mutex); +} const static struct dma_buf_ops radeon_dmabuf_ops = { .map_dma_buf = radeon_gem_map_dma_buf, .unmap_dma_buf = radeon_gem_unmap_dma_buf, @@ -93,6 +134,9 @@ const static struct dma_buf_ops radeon_dmabuf_ops = { .kmap_atomic = radeon_gem_kmap_atomic, .kunmap = radeon_gem_kunmap, .kunmap_atomic = radeon_gem_kunmap_atomic, + .mmap = radeon_gem_prime_mmap, + .vmap = radeon_gem_prime_vmap, + .vunmap = radeon_gem_prime_vunmap, }; static int radeon_prime_create(struct drm_device *dev, diff --git a/drivers/gpu/drm/radeon/radeon_ring.c b/drivers/gpu/drm/radeon/radeon_ring.c index 493a7be7530..983658c9135 100644 --- a/drivers/gpu/drm/radeon/radeon_ring.c +++ b/drivers/gpu/drm/radeon/radeon_ring.c @@ -39,31 +39,6 @@ */ int radeon_debugfs_sa_init(struct radeon_device *rdev); -u32 radeon_get_ib_value(struct radeon_cs_parser *p, int idx) -{ - struct radeon_cs_chunk *ibc = &p->chunks[p->chunk_ib_idx]; - u32 pg_idx, pg_offset; - u32 idx_value = 0; - int new_page; - - pg_idx = (idx * 4) / PAGE_SIZE; - pg_offset = (idx * 4) % PAGE_SIZE; - - if (ibc->kpage_idx[0] == pg_idx) - return ibc->kpage[0][pg_offset/4]; - if (ibc->kpage_idx[1] == pg_idx) - return ibc->kpage[1][pg_offset/4]; - - new_page = radeon_cs_update_pages(p, pg_idx); - if (new_page < 0) { - p->parser_error = new_page; - return 0; - } - - idx_value = ibc->kpage[new_page][pg_offset/4]; - return idx_value; -} - int radeon_ib_get(struct radeon_device *rdev, int ring, struct radeon_ib *ib, unsigned size) { diff --git a/drivers/gpu/drm/udl/udl_fb.c b/drivers/gpu/drm/udl/udl_fb.c index a029ee39b0c..ce9a6117992 100644 --- a/drivers/gpu/drm/udl/udl_fb.c +++ b/drivers/gpu/drm/udl/udl_fb.c @@ -156,8 +156,17 @@ int udl_handle_damage(struct udl_framebuffer *fb, int x, int y, if (!fb->active_16) return 0; - if (!fb->obj->vmapping) - udl_gem_vmap(fb->obj); + if (!fb->obj->vmapping) { + ret = udl_gem_vmap(fb->obj); + if (ret == -ENOMEM) { + DRM_ERROR("failed to vmap fb\n"); + return 0; + } + if (!fb->obj->vmapping) { + DRM_ERROR("failed to vmapping\n"); + return 0; + } + } start_cycles = get_cycles(); diff --git a/drivers/gpu/drm/udl/udl_gem.c b/drivers/gpu/drm/udl/udl_gem.c index 40efd32f7dc..7bd65bdd15a 100644 --- a/drivers/gpu/drm/udl/udl_gem.c +++ b/drivers/gpu/drm/udl/udl_gem.c @@ -180,6 +180,18 @@ int udl_gem_vmap(struct udl_gem_object *obj) int page_count = obj->base.size / PAGE_SIZE; int ret; + if (obj->base.import_attach) { + ret = dma_buf_begin_cpu_access(obj->base.import_attach->dmabuf, + 0, obj->base.size, DMA_BIDIRECTIONAL); + if (ret) + return -EINVAL; + + obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf); + if (!obj->vmapping) + return -ENOMEM; + return 0; + } + ret = udl_gem_get_pages(obj, GFP_KERNEL); if (ret) return ret; @@ -192,6 +204,13 @@ int udl_gem_vmap(struct udl_gem_object *obj) void udl_gem_vunmap(struct udl_gem_object *obj) { + if (obj->base.import_attach) { + dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping); + dma_buf_end_cpu_access(obj->base.import_attach->dmabuf, 0, + obj->base.size, DMA_BIDIRECTIONAL); + return; + } + if (obj->vmapping) vunmap(obj->vmapping); @@ -202,12 +221,12 @@ void udl_gem_free_object(struct drm_gem_object *gem_obj) { struct udl_gem_object *obj = to_udl_bo(gem_obj); - if (gem_obj->import_attach) - drm_prime_gem_destroy(gem_obj, obj->sg); - if (obj->vmapping) udl_gem_vunmap(obj); + if (gem_obj->import_attach) + drm_prime_gem_destroy(gem_obj, obj->sg); + if (obj->pages) udl_gem_put_pages(obj); @@ -234,7 +253,7 @@ int udl_gem_mmap(struct drm_file *file, struct drm_device *dev, ret = udl_gem_get_pages(gobj, GFP_KERNEL); if (ret) - return ret; + goto out; if (!gobj->base.map_list.map) { ret = drm_gem_create_mmap_offset(obj); if (ret) @@ -257,8 +276,6 @@ static int udl_prime_create(struct drm_device *dev, { struct udl_gem_object *obj; int npages; - int i; - struct scatterlist *iter; npages = size / PAGE_SIZE; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7cd9bf42108..6f1d167cb1e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1036,8 +1036,9 @@ config SENSORS_SCH56XX_COMMON config SENSORS_SCH5627 tristate "SMSC SCH5627" - depends on !PPC + depends on !PPC && WATCHDOG select SENSORS_SCH56XX_COMMON + select WATCHDOG_CORE help If you say yes here you get support for the hardware monitoring features of the SMSC SCH5627 Super-I/O chip including support for @@ -1048,8 +1049,9 @@ config SENSORS_SCH5627 config SENSORS_SCH5636 tristate "SMSC SCH5636" - depends on !PPC + depends on !PPC && WATCHDOG select SENSORS_SCH56XX_COMMON + select WATCHDOG_CORE help SMSC SCH5636 Super I/O chips include an embedded microcontroller for hardware monitoring solutions, allowing motherboard manufacturers to diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 8ec6dfbccb6..8342275378b 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c @@ -579,7 +579,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev) } /* Note failing to register the watchdog is not a fatal error */ - data->watchdog = sch56xx_watchdog_register(data->addr, + data->watchdog = sch56xx_watchdog_register(&pdev->dev, data->addr, (build_code << 24) | (build_id << 8) | hwmon_rev, &data->update_lock, 1); diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c index 906d4ed32d8..96a7e68718c 100644 --- a/drivers/hwmon/sch5636.c +++ b/drivers/hwmon/sch5636.c @@ -510,7 +510,7 @@ static int __devinit sch5636_probe(struct platform_device *pdev) } /* Note failing to register the watchdog is not a fatal error */ - data->watchdog = sch56xx_watchdog_register(data->addr, + data->watchdog = sch56xx_watchdog_register(&pdev->dev, data->addr, (revision[0] << 8) | revision[1], &data->update_lock, 0); diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index ce52fc57d41..4380f5d07be 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -66,15 +66,10 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" struct sch56xx_watchdog_data { u16 addr; - u32 revision; struct mutex *io_lock; - struct mutex watchdog_lock; - struct list_head list; /* member of the watchdog_data_list */ struct kref kref; - struct miscdevice watchdog_miscdev; - unsigned long watchdog_is_open; - char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ - char watchdog_expect_close; + struct watchdog_info wdinfo; + struct watchdog_device wddev; u8 watchdog_preset; u8 watchdog_control; u8 watchdog_output_enable; @@ -82,15 +77,6 @@ struct sch56xx_watchdog_data { static struct platform_device *sch56xx_pdev; -/* - * Somewhat ugly :( global data pointer list with all sch56xx devices, so that - * we can find our device data as when using misc_register there is no other - * method to get to ones device data from the open fop. - */ -static LIST_HEAD(watchdog_data_list); -/* Note this lock not only protect list access, but also data.kref access */ -static DEFINE_MUTEX(watchdog_data_mutex); - /* Super I/O functions */ static inline int superio_inb(int base, int reg) { @@ -272,22 +258,22 @@ EXPORT_SYMBOL(sch56xx_read_virtual_reg12); * Watchdog routines */ -/* - * Release our data struct when the platform device has been released *and* - * all references to our watchdog device are released. - */ -static void sch56xx_watchdog_release_resources(struct kref *r) +/* Release our data struct when we're unregistered *and* + all references to our watchdog device are released */ +static void watchdog_release_resources(struct kref *r) { struct sch56xx_watchdog_data *data = container_of(r, struct sch56xx_watchdog_data, kref); kfree(data); } -static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, - int timeout) +static int watchdog_set_timeout(struct watchdog_device *wddev, + unsigned int timeout) { - int ret, resolution; + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); + unsigned int resolution; u8 control; + int ret; /* 1 second or 60 second resolution? */ if (timeout <= 255) @@ -298,12 +284,6 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, if (timeout < resolution || timeout > (resolution * 255)) return -EINVAL; - mutex_lock(&data->watchdog_lock); - if (!data->addr) { - ret = -ENODEV; - goto leave; - } - if (resolution == 1) control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC; else @@ -316,7 +296,7 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, control); mutex_unlock(data->io_lock); if (ret) - goto leave; + return ret; data->watchdog_control = control; } @@ -326,38 +306,17 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, * the watchdog countdown. */ data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); + wddev->timeout = data->watchdog_preset * resolution; - ret = data->watchdog_preset * resolution; -leave: - mutex_unlock(&data->watchdog_lock); - return ret; -} - -static int watchdog_get_timeout(struct sch56xx_watchdog_data *data) -{ - int timeout; - - mutex_lock(&data->watchdog_lock); - if (data->watchdog_control & SCH56XX_WDOG_TIME_BASE_SEC) - timeout = data->watchdog_preset; - else - timeout = data->watchdog_preset * 60; - mutex_unlock(&data->watchdog_lock); - - return timeout; + return 0; } -static int watchdog_start(struct sch56xx_watchdog_data *data) +static int watchdog_start(struct watchdog_device *wddev) { + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); int ret; u8 val; - mutex_lock(&data->watchdog_lock); - if (!data->addr) { - ret = -ENODEV; - goto leave_unlock_watchdog; - } - /* * The sch56xx's watchdog cannot really be started / stopped * it is always running, but we can avoid the timer expiring @@ -385,18 +344,14 @@ static int watchdog_start(struct sch56xx_watchdog_data *data) if (ret) goto leave; - /* 2. Enable output (if not already enabled) */ - if (!(data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) { - val = data->watchdog_output_enable | - SCH56XX_WDOG_OUTPUT_ENABLE; - ret = sch56xx_write_virtual_reg(data->addr, - SCH56XX_REG_WDOG_OUTPUT_ENABLE, - val); - if (ret) - goto leave; + /* 2. Enable output */ + val = data->watchdog_output_enable | SCH56XX_WDOG_OUTPUT_ENABLE; + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_OUTPUT_ENABLE, val); + if (ret) + goto leave; - data->watchdog_output_enable = val; - } + data->watchdog_output_enable = val; /* 3. Clear the watchdog event bit if set */ val = inb(data->addr + 9); @@ -405,234 +360,70 @@ static int watchdog_start(struct sch56xx_watchdog_data *data) leave: mutex_unlock(data->io_lock); -leave_unlock_watchdog: - mutex_unlock(&data->watchdog_lock); return ret; } -static int watchdog_trigger(struct sch56xx_watchdog_data *data) +static int watchdog_trigger(struct watchdog_device *wddev) { + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); int ret; - mutex_lock(&data->watchdog_lock); - if (!data->addr) { - ret = -ENODEV; - goto leave; - } - /* Reset the watchdog countdown counter */ mutex_lock(data->io_lock); ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, data->watchdog_preset); mutex_unlock(data->io_lock); -leave: - mutex_unlock(&data->watchdog_lock); + return ret; } -static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data) +static int watchdog_stop(struct watchdog_device *wddev) { + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); int ret = 0; u8 val; - if (!data->addr) - return -ENODEV; - - if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) { - val = data->watchdog_output_enable & - ~SCH56XX_WDOG_OUTPUT_ENABLE; - mutex_lock(data->io_lock); - ret = sch56xx_write_virtual_reg(data->addr, - SCH56XX_REG_WDOG_OUTPUT_ENABLE, - val); - mutex_unlock(data->io_lock); - if (ret) - return ret; - - data->watchdog_output_enable = val; - } - - return ret; -} - -static int watchdog_stop(struct sch56xx_watchdog_data *data) -{ - int ret; - - mutex_lock(&data->watchdog_lock); - ret = watchdog_stop_unlocked(data); - mutex_unlock(&data->watchdog_lock); - - return ret; -} - -static int watchdog_release(struct inode *inode, struct file *filp) -{ - struct sch56xx_watchdog_data *data = filp->private_data; - - if (data->watchdog_expect_close) { - watchdog_stop(data); - data->watchdog_expect_close = 0; - } else { - watchdog_trigger(data); - pr_crit("unexpected close, not stopping watchdog!\n"); - } - - clear_bit(0, &data->watchdog_is_open); - - mutex_lock(&watchdog_data_mutex); - kref_put(&data->kref, sch56xx_watchdog_release_resources); - mutex_unlock(&watchdog_data_mutex); + val = data->watchdog_output_enable & ~SCH56XX_WDOG_OUTPUT_ENABLE; + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_OUTPUT_ENABLE, val); + mutex_unlock(data->io_lock); + if (ret) + return ret; + data->watchdog_output_enable = val; return 0; } -static int watchdog_open(struct inode *inode, struct file *filp) +static void watchdog_ref(struct watchdog_device *wddev) { - struct sch56xx_watchdog_data *pos, *data = NULL; - int ret, watchdog_is_open; - - /* - * We get called from drivers/char/misc.c with misc_mtx hold, and we - * call misc_register() from sch56xx_watchdog_probe() with - * watchdog_data_mutex hold, as misc_register() takes the misc_mtx - * lock, this is a possible deadlock, so we use mutex_trylock here. - */ - if (!mutex_trylock(&watchdog_data_mutex)) - return -ERESTARTSYS; - list_for_each_entry(pos, &watchdog_data_list, list) { - if (pos->watchdog_miscdev.minor == iminor(inode)) { - data = pos; - break; - } - } - /* Note we can never not have found data, so we don't check for this */ - watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); - if (!watchdog_is_open) - kref_get(&data->kref); - mutex_unlock(&watchdog_data_mutex); - - if (watchdog_is_open) - return -EBUSY; - - filp->private_data = data; - - /* Start the watchdog */ - ret = watchdog_start(data); - if (ret) { - watchdog_release(inode, filp); - return ret; - } + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); - return nonseekable_open(inode, filp); + kref_get(&data->kref); } -static ssize_t watchdog_write(struct file *filp, const char __user *buf, - size_t count, loff_t *offset) +static void watchdog_unref(struct watchdog_device *wddev) { - int ret; - struct sch56xx_watchdog_data *data = filp->private_data; - - if (count) { - if (!nowayout) { - size_t i; - - /* Clear it in case it was set with a previous write */ - data->watchdog_expect_close = 0; - - for (i = 0; i != count; i++) { - char c; - if (get_user(c, buf + i)) - return -EFAULT; - if (c == 'V') - data->watchdog_expect_close = 1; - } - } - ret = watchdog_trigger(data); - if (ret) - return ret; - } - return count; -} - -static long watchdog_ioctl(struct file *filp, unsigned int cmd, - unsigned long arg) -{ - struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, - .identity = "sch56xx watchdog" - }; - int i, ret = 0; - struct sch56xx_watchdog_data *data = filp->private_data; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ident.firmware_version = data->revision; - if (!nowayout) - ident.options |= WDIOF_MAGICCLOSE; - if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) - ret = -EFAULT; - break; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - ret = put_user(0, (int __user *)arg); - break; - - case WDIOC_KEEPALIVE: - ret = watchdog_trigger(data); - break; + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); - case WDIOC_GETTIMEOUT: - i = watchdog_get_timeout(data); - ret = put_user(i, (int __user *)arg); - break; - - case WDIOC_SETTIMEOUT: - if (get_user(i, (int __user *)arg)) { - ret = -EFAULT; - break; - } - ret = watchdog_set_timeout(data, i); - if (ret >= 0) - ret = put_user(ret, (int __user *)arg); - break; - - case WDIOC_SETOPTIONS: - if (get_user(i, (int __user *)arg)) { - ret = -EFAULT; - break; - } - - if (i & WDIOS_DISABLECARD) - ret = watchdog_stop(data); - else if (i & WDIOS_ENABLECARD) - ret = watchdog_trigger(data); - else - ret = -EINVAL; - break; - - default: - ret = -ENOTTY; - } - return ret; + kref_put(&data->kref, watchdog_release_resources); } -static const struct file_operations watchdog_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .open = watchdog_open, - .release = watchdog_release, - .write = watchdog_write, - .unlocked_ioctl = watchdog_ioctl, +static const struct watchdog_ops watchdog_ops = { + .owner = THIS_MODULE, + .start = watchdog_start, + .stop = watchdog_stop, + .ping = watchdog_trigger, + .set_timeout = watchdog_set_timeout, + .ref = watchdog_ref, + .unref = watchdog_unref, }; -struct sch56xx_watchdog_data *sch56xx_watchdog_register( +struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision, struct mutex *io_lock, int check_enabled) { struct sch56xx_watchdog_data *data; - int i, err, control, output_enable; - const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; + int err, control, output_enable; /* Cache the watchdog registers */ mutex_lock(io_lock); @@ -656,82 +447,55 @@ struct sch56xx_watchdog_data *sch56xx_watchdog_register( return NULL; data->addr = addr; - data->revision = revision; data->io_lock = io_lock; - data->watchdog_control = control; - data->watchdog_output_enable = output_enable; - mutex_init(&data->watchdog_lock); - INIT_LIST_HEAD(&data->list); kref_init(&data->kref); - err = watchdog_set_timeout(data, 60); - if (err < 0) - goto error; - - /* - * We take the data_mutex lock early so that watchdog_open() cannot - * run when misc_register() has completed, but we've not yet added - * our data to the watchdog_data_list. - */ - mutex_lock(&watchdog_data_mutex); - for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { - /* Register our watchdog part */ - snprintf(data->watchdog_name, sizeof(data->watchdog_name), - "watchdog%c", (i == 0) ? '\0' : ('0' + i)); - data->watchdog_miscdev.name = data->watchdog_name; - data->watchdog_miscdev.fops = &watchdog_fops; - data->watchdog_miscdev.minor = watchdog_minors[i]; - err = misc_register(&data->watchdog_miscdev); - if (err == -EBUSY) - continue; - if (err) - break; + strlcpy(data->wdinfo.identity, "sch56xx watchdog", + sizeof(data->wdinfo.identity)); + data->wdinfo.firmware_version = revision; + data->wdinfo.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT; + if (!nowayout) + data->wdinfo.options |= WDIOF_MAGICCLOSE; + + data->wddev.info = &data->wdinfo; + data->wddev.ops = &watchdog_ops; + data->wddev.parent = parent; + data->wddev.timeout = 60; + data->wddev.min_timeout = 1; + data->wddev.max_timeout = 255 * 60; + if (nowayout) + set_bit(WDOG_NO_WAY_OUT, &data->wddev.status); + if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) + set_bit(WDOG_ACTIVE, &data->wddev.status); + + /* Since the watchdog uses a downcounter there is no register to read + the BIOS set timeout from (if any was set at all) -> + Choose a preset which will give us a 1 minute timeout */ + if (control & SCH56XX_WDOG_TIME_BASE_SEC) + data->watchdog_preset = 60; /* seconds */ + else + data->watchdog_preset = 1; /* minute */ - list_add(&data->list, &watchdog_data_list); - pr_info("Registered /dev/%s chardev major 10, minor: %d\n", - data->watchdog_name, watchdog_minors[i]); - break; - } - mutex_unlock(&watchdog_data_mutex); + data->watchdog_control = control; + data->watchdog_output_enable = output_enable; + watchdog_set_drvdata(&data->wddev, data); + err = watchdog_register_device(&data->wddev); if (err) { pr_err("Registering watchdog chardev: %d\n", err); - goto error; - } - if (i == ARRAY_SIZE(watchdog_minors)) { - pr_warn("Couldn't register watchdog (no free minor)\n"); - goto error; + kfree(data); + return NULL; } return data; - -error: - kfree(data); - return NULL; } EXPORT_SYMBOL(sch56xx_watchdog_register); void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data) { - mutex_lock(&watchdog_data_mutex); - misc_deregister(&data->watchdog_miscdev); - list_del(&data->list); - mutex_unlock(&watchdog_data_mutex); - - mutex_lock(&data->watchdog_lock); - if (data->watchdog_is_open) { - pr_warn("platform device unregistered with watchdog " - "open! Stopping watchdog.\n"); - watchdog_stop_unlocked(data); - } - /* Tell the wdog start/stop/trigger functions our dev is gone */ - data->addr = 0; - data->io_lock = NULL; - mutex_unlock(&data->watchdog_lock); - - mutex_lock(&watchdog_data_mutex); - kref_put(&data->kref, sch56xx_watchdog_release_resources); - mutex_unlock(&watchdog_data_mutex); + watchdog_unregister_device(&data->wddev); + kref_put(&data->kref, watchdog_release_resources); + /* Don't touch data after this it may have been free-ed! */ } EXPORT_SYMBOL(sch56xx_watchdog_unregister); diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h index 7475086eb97..704ea2c6d28 100644 --- a/drivers/hwmon/sch56xx-common.h +++ b/drivers/hwmon/sch56xx-common.h @@ -27,6 +27,6 @@ int sch56xx_read_virtual_reg16(u16 addr, u16 reg); int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, int high_nibble); -struct sch56xx_watchdog_data *sch56xx_watchdog_register( +struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision, struct mutex *io_lock, int check_enabled); void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data); diff --git a/drivers/i2c/algos/i2c-algo-bit.c b/drivers/i2c/algos/i2c-algo-bit.c index 7f0b8321974..fad22b0bb5b 100644 --- a/drivers/i2c/algos/i2c-algo-bit.c +++ b/drivers/i2c/algos/i2c-algo-bit.c @@ -608,7 +608,7 @@ bailout: static u32 bit_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL | I2C_FUNC_10BIT_ADDR | I2C_FUNC_PROTOCOL_MANGLING; diff --git a/drivers/i2c/busses/i2c-nuc900.c b/drivers/i2c/busses/i2c-nuc900.c index 03b61577888..a26dfb8cd58 100644 --- a/drivers/i2c/busses/i2c-nuc900.c +++ b/drivers/i2c/busses/i2c-nuc900.c @@ -502,7 +502,8 @@ static int nuc900_i2c_xfer(struct i2c_adapter *adap, /* declare our i2c functionality */ static u32 nuc900_i2c_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART | + I2C_FUNC_PROTOCOL_MANGLING; } /* i2c bus registration info */ diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c index fa0b1349087..01959154572 100644 --- a/drivers/i2c/busses/i2c-s3c2410.c +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -626,7 +626,8 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, /* declare our i2c functionality */ static u32 s3c24xx_i2c_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART | + I2C_FUNC_PROTOCOL_MANGLING; } /* i2c bus registration info */ diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index 45048323b75..5ec2261574e 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -265,19 +265,41 @@ static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client, res = 0; for (i = 0; i < rdwr_arg.nmsgs; i++) { - /* Limit the size of the message to a sane amount; - * and don't let length change either. */ - if ((rdwr_pa[i].len > 8192) || - (rdwr_pa[i].flags & I2C_M_RECV_LEN)) { + /* Limit the size of the message to a sane amount */ + if (rdwr_pa[i].len > 8192) { res = -EINVAL; break; } + data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf; rdwr_pa[i].buf = memdup_user(data_ptrs[i], rdwr_pa[i].len); if (IS_ERR(rdwr_pa[i].buf)) { res = PTR_ERR(rdwr_pa[i].buf); break; } + + /* + * If the message length is received from the slave (similar + * to SMBus block read), we must ensure that the buffer will + * be large enough to cope with a message length of + * I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus + * drivers allow. The first byte in the buffer must be + * pre-filled with the number of extra bytes, which must be + * at least one to hold the message length, but can be + * greater (for example to account for a checksum byte at + * the end of the message.) + */ + if (rdwr_pa[i].flags & I2C_M_RECV_LEN) { + if (!(rdwr_pa[i].flags & I2C_M_RD) || + rdwr_pa[i].buf[0] < 1 || + rdwr_pa[i].len < rdwr_pa[i].buf[0] + + I2C_SMBUS_BLOCK_MAX) { + res = -EINVAL; + break; + } + + rdwr_pa[i].len = rdwr_pa[i].buf[0]; + } } if (res < 0) { int j; diff --git a/drivers/input/joystick/as5011.c b/drivers/input/joystick/as5011.c index 3063464474b..57d19d4e0a2 100644 --- a/drivers/input/joystick/as5011.c +++ b/drivers/input/joystick/as5011.c @@ -231,6 +231,7 @@ static int __devinit as5011_probe(struct i2c_client *client, } if (!i2c_check_functionality(client->adapter, + I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING)) { dev_err(&client->dev, "need i2c bus that supports protocol mangling\n"); diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index a5bee8e2dfc..d90a421e9ca 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -450,12 +450,27 @@ static void dump_command(unsigned long phys_addr) static void iommu_print_event(struct amd_iommu *iommu, void *__evt) { - u32 *event = __evt; - int type = (event[1] >> EVENT_TYPE_SHIFT) & EVENT_TYPE_MASK; - int devid = (event[0] >> EVENT_DEVID_SHIFT) & EVENT_DEVID_MASK; - int domid = (event[1] >> EVENT_DOMID_SHIFT) & EVENT_DOMID_MASK; - int flags = (event[1] >> EVENT_FLAGS_SHIFT) & EVENT_FLAGS_MASK; - u64 address = (u64)(((u64)event[3]) << 32) | event[2]; + int type, devid, domid, flags; + volatile u32 *event = __evt; + int count = 0; + u64 address; + +retry: + type = (event[1] >> EVENT_TYPE_SHIFT) & EVENT_TYPE_MASK; + devid = (event[0] >> EVENT_DEVID_SHIFT) & EVENT_DEVID_MASK; + domid = (event[1] >> EVENT_DOMID_SHIFT) & EVENT_DOMID_MASK; + flags = (event[1] >> EVENT_FLAGS_SHIFT) & EVENT_FLAGS_MASK; + address = (u64)(((u64)event[3]) << 32) | event[2]; + + if (type == 0) { + /* Did we hit the erratum? */ + if (++count == LOOP_TIMEOUT) { + pr_err("AMD-Vi: No event written to event log\n"); + return; + } + udelay(1); + goto retry; + } printk(KERN_ERR "AMD-Vi: Event logged ["); @@ -508,6 +523,8 @@ static void iommu_print_event(struct amd_iommu *iommu, void *__evt) default: printk(KERN_ERR "UNKNOWN type=0x%02x]\n", type); } + + memset(__evt, 0, 4 * sizeof(u32)); } static void iommu_poll_events(struct amd_iommu *iommu) @@ -2035,20 +2052,20 @@ out_err: } /* FIXME: Move this to PCI code */ -#define PCI_PRI_TLP_OFF (1 << 2) +#define PCI_PRI_TLP_OFF (1 << 15) bool pci_pri_tlp_required(struct pci_dev *pdev) { - u16 control; + u16 status; int pos; pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_PRI); if (!pos) return false; - pci_read_config_word(pdev, pos + PCI_PRI_CTRL, &control); + pci_read_config_word(pdev, pos + PCI_PRI_STATUS, &status); - return (control & PCI_PRI_TLP_OFF) ? true : false; + return (status & PCI_PRI_TLP_OFF) ? true : false; } /* diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 2198b2dbbcd..8b9ded88e6f 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -119,6 +119,7 @@ EXPORT_SYMBOL_GPL(iommu_present); * iommu_set_fault_handler() - set a fault handler for an iommu domain * @domain: iommu domain * @handler: fault handler + * @token: user data, will be passed back to the fault handler * * This function should be used by IOMMU users which want to be notified * whenever an IOMMU fault happens. @@ -127,11 +128,13 @@ EXPORT_SYMBOL_GPL(iommu_present); * error code otherwise. */ void iommu_set_fault_handler(struct iommu_domain *domain, - iommu_fault_handler_t handler) + iommu_fault_handler_t handler, + void *token) { BUG_ON(!domain); domain->handler = handler; + domain->handler_token = token; } EXPORT_SYMBOL_GPL(iommu_set_fault_handler); diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c index 6899dcd02df..e70ee2b59df 100644 --- a/drivers/iommu/omap-iommu.c +++ b/drivers/iommu/omap-iommu.c @@ -41,11 +41,13 @@ * @pgtable: the page table * @iommu_dev: an omap iommu device attached to this domain. only a single * iommu device can be attached for now. + * @dev: Device using this domain. * @lock: domain lock, should be taken when attaching/detaching */ struct omap_iommu_domain { u32 *pgtable; struct omap_iommu *iommu_dev; + struct device *dev; spinlock_t lock; }; @@ -1081,6 +1083,7 @@ omap_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) } omap_domain->iommu_dev = arch_data->iommu_dev = oiommu; + omap_domain->dev = dev; oiommu->domain = domain; out: @@ -1088,19 +1091,16 @@ out: return ret; } -static void omap_iommu_detach_dev(struct iommu_domain *domain, - struct device *dev) +static void _omap_iommu_detach_dev(struct omap_iommu_domain *omap_domain, + struct device *dev) { - struct omap_iommu_domain *omap_domain = domain->priv; - struct omap_iommu_arch_data *arch_data = dev->archdata.iommu; struct omap_iommu *oiommu = dev_to_omap_iommu(dev); - - spin_lock(&omap_domain->lock); + struct omap_iommu_arch_data *arch_data = dev->archdata.iommu; /* only a single device is supported per domain for now */ if (omap_domain->iommu_dev != oiommu) { dev_err(dev, "invalid iommu device\n"); - goto out; + return; } iopgtable_clear_entry_all(oiommu); @@ -1108,8 +1108,16 @@ static void omap_iommu_detach_dev(struct iommu_domain *domain, omap_iommu_detach(oiommu); omap_domain->iommu_dev = arch_data->iommu_dev = NULL; + omap_domain->dev = NULL; +} -out: +static void omap_iommu_detach_dev(struct iommu_domain *domain, + struct device *dev) +{ + struct omap_iommu_domain *omap_domain = domain->priv; + + spin_lock(&omap_domain->lock); + _omap_iommu_detach_dev(omap_domain, dev); spin_unlock(&omap_domain->lock); } @@ -1148,13 +1156,19 @@ out: return -ENOMEM; } -/* assume device was already detached */ static void omap_iommu_domain_destroy(struct iommu_domain *domain) { struct omap_iommu_domain *omap_domain = domain->priv; domain->priv = NULL; + /* + * An iommu device is still attached + * (currently, only one device can be attached) ? + */ + if (omap_domain->iommu_dev) + _omap_iommu_detach_dev(omap_domain, omap_domain->dev); + kfree(omap_domain->pgtable); kfree(omap_domain); } diff --git a/drivers/iommu/tegra-gart.c b/drivers/iommu/tegra-gart.c index 779306ee7b1..0c0a3779221 100644 --- a/drivers/iommu/tegra-gart.c +++ b/drivers/iommu/tegra-gart.c @@ -29,15 +29,17 @@ #include <linux/device.h> #include <linux/io.h> #include <linux/iommu.h> +#include <linux/of.h> #include <asm/cacheflush.h> /* bitmap of the page sizes currently supported */ #define GART_IOMMU_PGSIZES (SZ_4K) -#define GART_CONFIG 0x24 -#define GART_ENTRY_ADDR 0x28 -#define GART_ENTRY_DATA 0x2c +#define GART_REG_BASE 0x24 +#define GART_CONFIG (0x24 - GART_REG_BASE) +#define GART_ENTRY_ADDR (0x28 - GART_REG_BASE) +#define GART_ENTRY_DATA (0x2c - GART_REG_BASE) #define GART_ENTRY_PHYS_ADDR_VALID (1 << 31) #define GART_PAGE_SHIFT 12 @@ -158,7 +160,7 @@ static int gart_iommu_attach_dev(struct iommu_domain *domain, struct gart_client *client, *c; int err = 0; - gart = dev_get_drvdata(dev->parent); + gart = gart_handle; if (!gart) return -EINVAL; domain->priv = gart; @@ -422,6 +424,14 @@ const struct dev_pm_ops tegra_gart_pm_ops = { .resume = tegra_gart_resume, }; +#ifdef CONFIG_OF +static struct of_device_id tegra_gart_of_match[] __devinitdata = { + { .compatible = "nvidia,tegra20-gart", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_gart_of_match); +#endif + static struct platform_driver tegra_gart_driver = { .probe = tegra_gart_probe, .remove = tegra_gart_remove, @@ -429,6 +439,7 @@ static struct platform_driver tegra_gart_driver = { .owner = THIS_MODULE, .name = "tegra-gart", .pm = &tegra_gart_pm_ops, + .of_match_table = of_match_ptr(tegra_gart_of_match), }, }; @@ -448,4 +459,5 @@ module_exit(tegra_gart_exit); MODULE_DESCRIPTION("IOMMU API for GART in Tegra20"); MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>"); +MODULE_ALIAS("platform:tegra-gart"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c index eb93c821f59..ecd679043d7 100644 --- a/drivers/iommu/tegra-smmu.c +++ b/drivers/iommu/tegra-smmu.c @@ -733,7 +733,7 @@ static int smmu_iommu_attach_dev(struct iommu_domain *domain, pr_info("Reserve \"page zero\" for AVP vectors using a common dummy\n"); } - dev_dbg(smmu->dev, "%s is attached\n", dev_name(c->dev)); + dev_dbg(smmu->dev, "%s is attached\n", dev_name(dev)); return 0; err_client: diff --git a/drivers/message/fusion/mptbase.c b/drivers/message/fusion/mptbase.c index 48052b62ee0..d99db5623ac 100644 --- a/drivers/message/fusion/mptbase.c +++ b/drivers/message/fusion/mptbase.c @@ -6479,6 +6479,7 @@ mpt_config(MPT_ADAPTER *ioc, CONFIGPARMS *pCfg) printk(MYIOC_s_INFO_FMT "%s: host reset in" " progress mpt_config timed out.!!\n", __func__, ioc->name); + mutex_unlock(&ioc->mptbase_cmds.mutex); return -EFAULT; } spin_unlock_irqrestore(&ioc->taskmgmt_lock, flags); diff --git a/drivers/mtd/maps/lantiq-flash.c b/drivers/mtd/maps/lantiq-flash.c index b5401e35574..c03456f1700 100644 --- a/drivers/mtd/maps/lantiq-flash.c +++ b/drivers/mtd/maps/lantiq-flash.c @@ -19,9 +19,9 @@ #include <linux/mtd/cfi.h> #include <linux/platform_device.h> #include <linux/mtd/physmap.h> +#include <linux/of.h> #include <lantiq_soc.h> -#include <lantiq_platform.h> /* * The NOR flash is connected to the same external bus unit (EBU) as PCI. @@ -44,8 +44,9 @@ struct ltq_mtd { struct map_info *map; }; -static char ltq_map_name[] = "ltq_nor"; -static const char *ltq_probe_types[] __devinitconst = { "cmdlinepart", NULL }; +static const char ltq_map_name[] = "ltq_nor"; +static const char *ltq_probe_types[] __devinitconst = { + "cmdlinepart", "ofpart", NULL }; static map_word ltq_read16(struct map_info *map, unsigned long adr) @@ -108,42 +109,38 @@ ltq_copy_to(struct map_info *map, unsigned long to, spin_unlock_irqrestore(&ebu_lock, flags); } -static int __init +static int __devinit ltq_mtd_probe(struct platform_device *pdev) { - struct physmap_flash_data *ltq_mtd_data = dev_get_platdata(&pdev->dev); + struct mtd_part_parser_data ppdata; struct ltq_mtd *ltq_mtd; - struct resource *res; struct cfi_private *cfi; int err; + if (of_machine_is_compatible("lantiq,falcon") && + (ltq_boot_select() != BS_FLASH)) { + dev_err(&pdev->dev, "invalid bootstrap options\n"); + return -ENODEV; + } + ltq_mtd = kzalloc(sizeof(struct ltq_mtd), GFP_KERNEL); platform_set_drvdata(pdev, ltq_mtd); ltq_mtd->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!ltq_mtd->res) { - dev_err(&pdev->dev, "failed to get memory resource"); + dev_err(&pdev->dev, "failed to get memory resource\n"); err = -ENOENT; goto err_out; } - res = devm_request_mem_region(&pdev->dev, ltq_mtd->res->start, - resource_size(ltq_mtd->res), dev_name(&pdev->dev)); - if (!ltq_mtd->res) { - dev_err(&pdev->dev, "failed to request mem resource"); - err = -EBUSY; - goto err_out; - } - ltq_mtd->map = kzalloc(sizeof(struct map_info), GFP_KERNEL); - ltq_mtd->map->phys = res->start; - ltq_mtd->map->size = resource_size(res); - ltq_mtd->map->virt = devm_ioremap_nocache(&pdev->dev, - ltq_mtd->map->phys, ltq_mtd->map->size); + ltq_mtd->map->phys = ltq_mtd->res->start; + ltq_mtd->map->size = resource_size(ltq_mtd->res); + ltq_mtd->map->virt = devm_request_and_ioremap(&pdev->dev, ltq_mtd->res); if (!ltq_mtd->map->virt) { - dev_err(&pdev->dev, "failed to ioremap!\n"); - err = -ENOMEM; - goto err_free; + dev_err(&pdev->dev, "failed to remap mem resource\n"); + err = -EBUSY; + goto err_out; } ltq_mtd->map->name = ltq_map_name; @@ -169,9 +166,9 @@ ltq_mtd_probe(struct platform_device *pdev) cfi->addr_unlock1 ^= 1; cfi->addr_unlock2 ^= 1; - err = mtd_device_parse_register(ltq_mtd->mtd, ltq_probe_types, NULL, - ltq_mtd_data->parts, - ltq_mtd_data->nr_parts); + ppdata.of_node = pdev->dev.of_node; + err = mtd_device_parse_register(ltq_mtd->mtd, ltq_probe_types, + &ppdata, NULL, 0); if (err) { dev_err(&pdev->dev, "failed to add partitions\n"); goto err_destroy; @@ -204,32 +201,23 @@ ltq_mtd_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ltq_mtd_match[] = { + { .compatible = "lantiq,nor" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_mtd_match); + static struct platform_driver ltq_mtd_driver = { + .probe = ltq_mtd_probe, .remove = __devexit_p(ltq_mtd_remove), .driver = { - .name = "ltq_nor", + .name = "ltq-nor", .owner = THIS_MODULE, + .of_match_table = ltq_mtd_match, }, }; -static int __init -init_ltq_mtd(void) -{ - int ret = platform_driver_probe(<q_mtd_driver, ltq_mtd_probe); - - if (ret) - pr_err("ltq_nor: error registering platform driver"); - return ret; -} - -static void __exit -exit_ltq_mtd(void) -{ - platform_driver_unregister(<q_mtd_driver); -} - -module_init(init_ltq_mtd); -module_exit(exit_ltq_mtd); +module_platform_driver(ltq_mtd_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); diff --git a/drivers/net/ethernet/rdc/r6040.c b/drivers/net/ethernet/rdc/r6040.c index 4de73643fec..d1827e887f4 100644 --- a/drivers/net/ethernet/rdc/r6040.c +++ b/drivers/net/ethernet/rdc/r6040.c @@ -1096,20 +1096,20 @@ static int __devinit r6040_init_one(struct pci_dev *pdev, if (err) { dev_err(&pdev->dev, "32-bit PCI DMA addresses" "not supported by the card\n"); - goto err_out; + goto err_out_disable_dev; } err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); if (err) { dev_err(&pdev->dev, "32-bit PCI DMA addresses" "not supported by the card\n"); - goto err_out; + goto err_out_disable_dev; } /* IO Size check */ if (pci_resource_len(pdev, bar) < io_size) { dev_err(&pdev->dev, "Insufficient PCI resources, aborting\n"); err = -EIO; - goto err_out; + goto err_out_disable_dev; } pci_set_master(pdev); @@ -1117,7 +1117,7 @@ static int __devinit r6040_init_one(struct pci_dev *pdev, dev = alloc_etherdev(sizeof(struct r6040_private)); if (!dev) { err = -ENOMEM; - goto err_out; + goto err_out_disable_dev; } SET_NETDEV_DEV(dev, &pdev->dev); lp = netdev_priv(dev); @@ -1233,11 +1233,15 @@ err_out_mdio_irq: err_out_mdio: mdiobus_free(lp->mii_bus); err_out_unmap: + netif_napi_del(&lp->napi); + pci_set_drvdata(pdev, NULL); pci_iounmap(pdev, ioaddr); err_out_free_res: pci_release_regions(pdev); err_out_free_dev: free_netdev(dev); +err_out_disable_dev: + pci_disable_device(pdev); err_out: return err; } @@ -1251,6 +1255,9 @@ static void __devexit r6040_remove_one(struct pci_dev *pdev) mdiobus_unregister(lp->mii_bus); kfree(lp->mii_bus->irq); mdiobus_free(lp->mii_bus); + netif_napi_del(&lp->napi); + pci_set_drvdata(pdev, NULL); + pci_iounmap(pdev, lp->base); pci_release_regions(pdev); free_netdev(dev); pci_disable_device(pdev); diff --git a/drivers/net/ethernet/renesas/sh_eth.c b/drivers/net/ethernet/renesas/sh_eth.c index be3c2217916..667169b8252 100644 --- a/drivers/net/ethernet/renesas/sh_eth.c +++ b/drivers/net/ethernet/renesas/sh_eth.c @@ -1101,8 +1101,12 @@ static int sh_eth_rx(struct net_device *ndev) /* Restart Rx engine if stopped. */ /* If we don't need to check status, don't. -KDU */ - if (!(sh_eth_read(ndev, EDRRR) & EDRRR_R)) + if (!(sh_eth_read(ndev, EDRRR) & EDRRR_R)) { + /* fix the values for the next receiving */ + mdp->cur_rx = mdp->dirty_rx = (sh_eth_read(ndev, RDFAR) - + sh_eth_read(ndev, RDLAR)) >> 4; sh_eth_write(ndev, EDRRR_R, EDRRR); + } return 0; } @@ -1199,8 +1203,6 @@ static void sh_eth_error(struct net_device *ndev, int intr_status) /* Receive Descriptor Empty int */ ndev->stats.rx_over_errors++; - if (sh_eth_read(ndev, EDRRR) ^ EDRRR_R) - sh_eth_write(ndev, EDRRR_R, EDRRR); if (netif_msg_rx_err(mdp)) dev_err(&ndev->dev, "Receive Descriptor Empty\n"); } diff --git a/drivers/net/ethernet/smsc/smsc911x.c b/drivers/net/ethernet/smsc/smsc911x.c index dab9c6f671e..1466e5d2af4 100644 --- a/drivers/net/ethernet/smsc/smsc911x.c +++ b/drivers/net/ethernet/smsc/smsc911x.c @@ -2390,11 +2390,11 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) retval = smsc911x_request_resources(pdev); if (retval) - goto out_return_resources; + goto out_request_resources_fail; retval = smsc911x_enable_resources(pdev); if (retval) - goto out_disable_resources; + goto out_enable_resources_fail; if (pdata->ioaddr == NULL) { SMSC_WARN(pdata, probe, "Error smsc911x base address invalid"); @@ -2501,8 +2501,9 @@ out_free_irq: free_irq(dev->irq, dev); out_disable_resources: (void)smsc911x_disable_resources(pdev); -out_return_resources: +out_enable_resources_fail: smsc911x_free_resources(pdev); +out_request_resources_fail: platform_set_drvdata(pdev, NULL); iounmap(pdata->ioaddr); free_netdev(dev); diff --git a/drivers/net/usb/asix.c b/drivers/net/usb/asix.c index 71e2b0523bc..3ae80eccd0e 100644 --- a/drivers/net/usb/asix.c +++ b/drivers/net/usb/asix.c @@ -35,6 +35,7 @@ #include <linux/crc32.h> #include <linux/usb/usbnet.h> #include <linux/slab.h> +#include <linux/if_vlan.h> #define DRIVER_VERSION "22-Dec-2011" #define DRIVER_NAME "asix" @@ -321,7 +322,7 @@ static int asix_rx_fixup(struct usbnet *dev, struct sk_buff *skb) return 0; } - if ((size > dev->net->mtu + ETH_HLEN) || + if ((size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) || (size + offset > skb->len)) { netdev_err(dev->net, "asix_rx_fixup() Bad RX Length %d\n", size); diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 380dbea6109..3b206786b5e 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -547,6 +547,8 @@ static const struct usb_device_id products[] = { {QMI_GOBI_DEVICE(0x16d8, 0x8002)}, /* CMDTech Gobi 2000 Modem device (VU922) */ {QMI_GOBI_DEVICE(0x05c6, 0x9205)}, /* Gobi 2000 Modem device */ {QMI_GOBI_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */ + {QMI_GOBI_DEVICE(0x1199, 0x9015)}, /* Sierra Wireless Gobi 3000 Modem device */ + {QMI_GOBI_DEVICE(0x1199, 0x9019)}, /* Sierra Wireless Gobi 3000 Modem device */ { } /* END */ }; MODULE_DEVICE_TABLE(usb, products); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 9ce6995e8d0..5214b1eceb9 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -1231,11 +1231,6 @@ static int virtnet_freeze(struct virtio_device *vdev) vi->config_enable = false; mutex_unlock(&vi->config_lock); - virtqueue_disable_cb(vi->rvq); - virtqueue_disable_cb(vi->svq); - if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_CTRL_VQ)) - virtqueue_disable_cb(vi->cvq); - netif_device_detach(vi->dev); cancel_delayed_work_sync(&vi->refill); diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 0ba81a66061..fbaa3093007 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -2415,6 +2415,22 @@ ath5k_tx_complete_poll_work(struct work_struct *work) * Initialization routines * \*************************/ +static const struct ieee80211_iface_limit if_limits[] = { + { .max = 2048, .types = BIT(NL80211_IFTYPE_STATION) }, + { .max = 4, .types = +#ifdef CONFIG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_AP) }, +}; + +static const struct ieee80211_iface_combination if_comb = { + .limits = if_limits, + .n_limits = ARRAY_SIZE(if_limits), + .max_interfaces = 2048, + .num_different_channels = 1, +}; + int __devinit ath5k_init_ah(struct ath5k_hw *ah, const struct ath_bus_ops *bus_ops) { @@ -2436,6 +2452,9 @@ ath5k_init_ah(struct ath5k_hw *ah, const struct ath_bus_ops *bus_ops) BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_MESH_POINT); + hw->wiphy->iface_combinations = &if_comb; + hw->wiphy->n_iface_combinations = 1; + /* SW support for IBSS_RSN is provided by mac80211 */ hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; diff --git a/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c index ac53d901801..dfb0441f406 100644 --- a/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c +++ b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c @@ -3809,7 +3809,7 @@ static bool is_pmu_set(struct ath_hw *ah, u32 pmu_reg, int pmu_set) return true; } -static void ar9003_hw_internal_regulator_apply(struct ath_hw *ah) +void ar9003_hw_internal_regulator_apply(struct ath_hw *ah) { int internal_regulator = ath9k_hw_ar9300_get_eeprom(ah, EEP_INTERNAL_REGULATOR); diff --git a/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h index 2505ac44f0c..8396d150ce0 100644 --- a/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h +++ b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h @@ -334,4 +334,7 @@ u8 *ar9003_get_spur_chan_ptr(struct ath_hw *ah, bool is_2ghz); unsigned int ar9003_get_paprd_scale_factor(struct ath_hw *ah, struct ath9k_channel *chan); + +void ar9003_hw_internal_regulator_apply(struct ath_hw *ah); + #endif diff --git a/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h b/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h index f11d9b2677f..1bd3a3d2210 100644 --- a/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h +++ b/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,7 +19,7 @@ #define INITVALS_9330_1P1_H static const u32 ar9331_1p1_baseband_postamble[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8005, 0xd00a8005}, {0x00009820, 0x206a002e, 0x206a002e, 0x206a002e, 0x206a002e}, {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, @@ -27,10 +28,10 @@ static const u32 ar9331_1p1_baseband_postamble[][5] = { {0x00009830, 0x0000059c, 0x0000059c, 0x0000059c, 0x0000059c}, {0x00009c00, 0x00000044, 0x00000044, 0x00000044, 0x00000044}, {0x00009e00, 0x0372161e, 0x0372161e, 0x037216a4, 0x037216a4}, - {0x00009e04, 0x00182020, 0x00182020, 0x00182020, 0x00182020}, + {0x00009e04, 0x00202020, 0x00202020, 0x00202020, 0x00202020}, {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, {0x00009e10, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e}, - {0x00009e14, 0x31395d5e, 0x3139605e, 0x3139605e, 0x31395d5e}, + {0x00009e14, 0x31365d5e, 0x3136605e, 0x3136605e, 0x31365d5e}, {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, @@ -55,7 +56,7 @@ static const u32 ar9331_1p1_baseband_postamble[][5] = { {0x0000a288, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x0000a28c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, - {0x0000a2d0, 0x00071981, 0x00071981, 0x00071981, 0x00071981}, + {0x0000a2d0, 0x00071982, 0x00071982, 0x00071982, 0x00071982}, {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0xf999a83a, 0xf999a83a}, {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x0000ae04, 0x00802020, 0x00802020, 0x00802020, 0x00802020}, @@ -63,7 +64,7 @@ static const u32 ar9331_1p1_baseband_postamble[][5] = { }; static const u32 ar9331_modes_lowest_ob_db_tx_gain_1p1[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, {0x0000a2dc, 0xffff2a52, 0xffff2a52, 0xffff2a52, 0xffff2a52}, {0x0000a2e0, 0xffffcc84, 0xffffcc84, 0xffffcc84, 0xffffcc84}, @@ -155,7 +156,7 @@ static const u32 ar9331_modes_lowest_ob_db_tx_gain_1p1[][5] = { }; static const u32 ar9331_modes_high_ob_db_tx_gain_1p1[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, {0x0000a2dc, 0xffaa9a52, 0xffaa9a52, 0xffaa9a52, 0xffaa9a52}, {0x0000a2e0, 0xffb31c84, 0xffb31c84, 0xffb31c84, 0xffb31c84}, @@ -245,7 +246,7 @@ static const u32 ar9331_modes_high_ob_db_tx_gain_1p1[][5] = { }; static const u32 ar9331_modes_low_ob_db_tx_gain_1p1[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, {0x0000a2dc, 0xffff2a52, 0xffff2a52, 0xffff2a52, 0xffff2a52}, {0x0000a2e0, 0xffffcc84, 0xffffcc84, 0xffffcc84, 0xffffcc84}, @@ -377,14 +378,14 @@ static const u32 ar9331_1p1_radio_core[][2] = { {0x000160b4, 0x92480040}, {0x000160c0, 0x006db6db}, {0x000160c4, 0x0186db60}, - {0x000160c8, 0x6db6db6c}, + {0x000160c8, 0x6db4db6c}, {0x000160cc, 0x6de6c300}, {0x000160d0, 0x14500820}, {0x00016100, 0x04cb0001}, {0x00016104, 0xfff80015}, {0x00016108, 0x00080010}, {0x0001610c, 0x00170000}, - {0x00016140, 0x10804000}, + {0x00016140, 0x10800000}, {0x00016144, 0x01884080}, {0x00016148, 0x000080c0}, {0x00016280, 0x01000015}, @@ -417,7 +418,7 @@ static const u32 ar9331_1p1_radio_core[][2] = { }; static const u32 ar9331_1p1_soc_postamble[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x00007010, 0x00000022, 0x00000022, 0x00000022, 0x00000022}, }; @@ -691,7 +692,7 @@ static const u32 ar9331_1p1_baseband_core[][2] = { }; static const u32 ar9331_modes_high_power_tx_gain_1p1[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, {0x0000a2dc, 0xffff2a52, 0xffff2a52, 0xffff2a52, 0xffff2a52}, {0x0000a2e0, 0xffffcc84, 0xffffcc84, 0xffffcc84, 0xffffcc84}, @@ -783,7 +784,7 @@ static const u32 ar9331_modes_high_power_tx_gain_1p1[][5] = { }; static const u32 ar9331_1p1_mac_postamble[][5] = { - /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, @@ -973,26 +974,27 @@ static const u32 ar9331_1p1_mac_core[][2] = { static const u32 ar9331_common_rx_gain_1p1[][2] = { /* Addr allmodes */ - {0x0000a000, 0x00010000}, - {0x0000a004, 0x00030002}, - {0x0000a008, 0x00050004}, - {0x0000a00c, 0x00810080}, - {0x0000a010, 0x00830082}, - {0x0000a014, 0x01810180}, - {0x0000a018, 0x01830182}, - {0x0000a01c, 0x01850184}, - {0x0000a020, 0x01890188}, - {0x0000a024, 0x018b018a}, - {0x0000a028, 0x018d018c}, - {0x0000a02c, 0x01910190}, - {0x0000a030, 0x01930192}, - {0x0000a034, 0x01950194}, - {0x0000a038, 0x038a0196}, - {0x0000a03c, 0x038c038b}, - {0x0000a040, 0x0390038d}, - {0x0000a044, 0x03920391}, - {0x0000a048, 0x03940393}, - {0x0000a04c, 0x03960395}, + {0x00009e18, 0x05000000}, + {0x0000a000, 0x00060005}, + {0x0000a004, 0x00810080}, + {0x0000a008, 0x00830082}, + {0x0000a00c, 0x00850084}, + {0x0000a010, 0x01820181}, + {0x0000a014, 0x01840183}, + {0x0000a018, 0x01880185}, + {0x0000a01c, 0x018a0189}, + {0x0000a020, 0x02850284}, + {0x0000a024, 0x02890288}, + {0x0000a028, 0x028b028a}, + {0x0000a02c, 0x03850384}, + {0x0000a030, 0x03890388}, + {0x0000a034, 0x038b038a}, + {0x0000a038, 0x038d038c}, + {0x0000a03c, 0x03910390}, + {0x0000a040, 0x03930392}, + {0x0000a044, 0x03950394}, + {0x0000a048, 0x00000396}, + {0x0000a04c, 0x00000000}, {0x0000a050, 0x00000000}, {0x0000a054, 0x00000000}, {0x0000a058, 0x00000000}, @@ -1005,15 +1007,15 @@ static const u32 ar9331_common_rx_gain_1p1[][2] = { {0x0000a074, 0x00000000}, {0x0000a078, 0x00000000}, {0x0000a07c, 0x00000000}, - {0x0000a080, 0x22222229}, - {0x0000a084, 0x1d1d1d1d}, - {0x0000a088, 0x1d1d1d1d}, - {0x0000a08c, 0x1d1d1d1d}, - {0x0000a090, 0x171d1d1d}, - {0x0000a094, 0x11111717}, - {0x0000a098, 0x00030311}, - {0x0000a09c, 0x00000000}, - {0x0000a0a0, 0x00000000}, + {0x0000a080, 0x28282828}, + {0x0000a084, 0x28282828}, + {0x0000a088, 0x28282828}, + {0x0000a08c, 0x28282828}, + {0x0000a090, 0x28282828}, + {0x0000a094, 0x24242428}, + {0x0000a098, 0x171e1e1e}, + {0x0000a09c, 0x02020b0b}, + {0x0000a0a0, 0x02020202}, {0x0000a0a4, 0x00000000}, {0x0000a0a8, 0x00000000}, {0x0000a0ac, 0x00000000}, @@ -1021,27 +1023,27 @@ static const u32 ar9331_common_rx_gain_1p1[][2] = { {0x0000a0b4, 0x00000000}, {0x0000a0b8, 0x00000000}, {0x0000a0bc, 0x00000000}, - {0x0000a0c0, 0x001f0000}, - {0x0000a0c4, 0x01000101}, - {0x0000a0c8, 0x011e011f}, - {0x0000a0cc, 0x011c011d}, - {0x0000a0d0, 0x02030204}, - {0x0000a0d4, 0x02010202}, - {0x0000a0d8, 0x021f0200}, - {0x0000a0dc, 0x0302021e}, - {0x0000a0e0, 0x03000301}, - {0x0000a0e4, 0x031e031f}, - {0x0000a0e8, 0x0402031d}, - {0x0000a0ec, 0x04000401}, - {0x0000a0f0, 0x041e041f}, - {0x0000a0f4, 0x0502041d}, - {0x0000a0f8, 0x05000501}, - {0x0000a0fc, 0x051e051f}, - {0x0000a100, 0x06010602}, - {0x0000a104, 0x061f0600}, - {0x0000a108, 0x061d061e}, - {0x0000a10c, 0x07020703}, - {0x0000a110, 0x07000701}, + {0x0000a0c0, 0x22072208}, + {0x0000a0c4, 0x22052206}, + {0x0000a0c8, 0x22032204}, + {0x0000a0cc, 0x22012202}, + {0x0000a0d0, 0x221f2200}, + {0x0000a0d4, 0x221d221e}, + {0x0000a0d8, 0x33023303}, + {0x0000a0dc, 0x33003301}, + {0x0000a0e0, 0x331e331f}, + {0x0000a0e4, 0x4402331d}, + {0x0000a0e8, 0x44004401}, + {0x0000a0ec, 0x441e441f}, + {0x0000a0f0, 0x55025503}, + {0x0000a0f4, 0x55005501}, + {0x0000a0f8, 0x551e551f}, + {0x0000a0fc, 0x6602551d}, + {0x0000a100, 0x66006601}, + {0x0000a104, 0x661e661f}, + {0x0000a108, 0x7703661d}, + {0x0000a10c, 0x77017702}, + {0x0000a110, 0x00007700}, {0x0000a114, 0x00000000}, {0x0000a118, 0x00000000}, {0x0000a11c, 0x00000000}, @@ -1054,26 +1056,26 @@ static const u32 ar9331_common_rx_gain_1p1[][2] = { {0x0000a138, 0x00000000}, {0x0000a13c, 0x00000000}, {0x0000a140, 0x001f0000}, - {0x0000a144, 0x01000101}, - {0x0000a148, 0x011e011f}, - {0x0000a14c, 0x011c011d}, - {0x0000a150, 0x02030204}, - {0x0000a154, 0x02010202}, - {0x0000a158, 0x021f0200}, - {0x0000a15c, 0x0302021e}, - {0x0000a160, 0x03000301}, - {0x0000a164, 0x031e031f}, - {0x0000a168, 0x0402031d}, - {0x0000a16c, 0x04000401}, - {0x0000a170, 0x041e041f}, - {0x0000a174, 0x0502041d}, - {0x0000a178, 0x05000501}, - {0x0000a17c, 0x051e051f}, - {0x0000a180, 0x06010602}, - {0x0000a184, 0x061f0600}, - {0x0000a188, 0x061d061e}, - {0x0000a18c, 0x07020703}, - {0x0000a190, 0x07000701}, + {0x0000a144, 0x111f1100}, + {0x0000a148, 0x111d111e}, + {0x0000a14c, 0x111b111c}, + {0x0000a150, 0x22032204}, + {0x0000a154, 0x22012202}, + {0x0000a158, 0x221f2200}, + {0x0000a15c, 0x221d221e}, + {0x0000a160, 0x33013302}, + {0x0000a164, 0x331f3300}, + {0x0000a168, 0x4402331e}, + {0x0000a16c, 0x44004401}, + {0x0000a170, 0x441e441f}, + {0x0000a174, 0x55015502}, + {0x0000a178, 0x551f5500}, + {0x0000a17c, 0x6602551e}, + {0x0000a180, 0x66006601}, + {0x0000a184, 0x661e661f}, + {0x0000a188, 0x7703661d}, + {0x0000a18c, 0x77017702}, + {0x0000a190, 0x00007700}, {0x0000a194, 0x00000000}, {0x0000a198, 0x00000000}, {0x0000a19c, 0x00000000}, @@ -1100,14 +1102,14 @@ static const u32 ar9331_common_rx_gain_1p1[][2] = { {0x0000a1f0, 0x00000396}, {0x0000a1f4, 0x00000396}, {0x0000a1f8, 0x00000396}, - {0x0000a1fc, 0x00000196}, + {0x0000a1fc, 0x00000296}, }; static const u32 ar9331_common_tx_gain_offset1_1[][1] = { - {0}, - {3}, - {0}, - {0}, + {0x00000000}, + {0x00000003}, + {0x00000000}, + {0x00000000}, }; static const u32 ar9331_1p1_chansel_xtal_25M[] = { diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c index abe05ec85d5..7db1890448f 100644 --- a/drivers/net/wireless/ath/ath9k/hw.c +++ b/drivers/net/wireless/ath/ath9k/hw.c @@ -1468,6 +1468,9 @@ static bool ath9k_hw_chip_reset(struct ath_hw *ah, return false; ah->chip_fullsleep = false; + + if (AR_SREV_9330(ah)) + ar9003_hw_internal_regulator_apply(ah); ath9k_hw_init_pll(ah, chan); ath9k_hw_set_rfmode(ah, chan); diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index dfa78e8b647..4de4473776a 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -239,7 +239,7 @@ static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush) { struct ath_hw *ah = sc->sc_ah; struct ath_common *common = ath9k_hw_common(ah); - bool ret; + bool ret = true; ieee80211_stop_queues(sc->hw); @@ -250,11 +250,12 @@ static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush) ath9k_debug_samp_bb_mac(sc); ath9k_hw_disable_interrupts(ah); - ret = ath_drain_all_txq(sc, retry_tx); - if (!ath_stoprecv(sc)) ret = false; + if (!ath_drain_all_txq(sc, retry_tx)) + ret = false; + if (!flush) { if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) ath_rx_tasklet(sc, 1, true); diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c index 23eaa1b26eb..d59dd01d6cd 100644 --- a/drivers/net/wireless/ath/ath9k/xmit.c +++ b/drivers/net/wireless/ath/ath9k/xmit.c @@ -64,7 +64,8 @@ static void ath_tx_update_baw(struct ath_softc *sc, struct ath_atx_tid *tid, static struct ath_buf *ath_tx_setup_buffer(struct ath_softc *sc, struct ath_txq *txq, struct ath_atx_tid *tid, - struct sk_buff *skb); + struct sk_buff *skb, + bool dequeue); enum { MCS_HT20, @@ -811,7 +812,7 @@ static enum ATH_AGGR_STATUS ath_tx_form_aggr(struct ath_softc *sc, fi = get_frame_info(skb); bf = fi->bf; if (!fi->bf) - bf = ath_tx_setup_buffer(sc, txq, tid, skb); + bf = ath_tx_setup_buffer(sc, txq, tid, skb, true); if (!bf) continue; @@ -1726,7 +1727,7 @@ static void ath_tx_send_ampdu(struct ath_softc *sc, struct ath_atx_tid *tid, return; } - bf = ath_tx_setup_buffer(sc, txctl->txq, tid, skb); + bf = ath_tx_setup_buffer(sc, txctl->txq, tid, skb, false); if (!bf) return; @@ -1753,7 +1754,7 @@ static void ath_tx_send_normal(struct ath_softc *sc, struct ath_txq *txq, bf = fi->bf; if (!bf) - bf = ath_tx_setup_buffer(sc, txq, tid, skb); + bf = ath_tx_setup_buffer(sc, txq, tid, skb, false); if (!bf) return; @@ -1814,7 +1815,8 @@ u8 ath_txchainmask_reduction(struct ath_softc *sc, u8 chainmask, u32 rate) static struct ath_buf *ath_tx_setup_buffer(struct ath_softc *sc, struct ath_txq *txq, struct ath_atx_tid *tid, - struct sk_buff *skb) + struct sk_buff *skb, + bool dequeue) { struct ath_common *common = ath9k_hw_common(sc->sc_ah); struct ath_frame_info *fi = get_frame_info(skb); @@ -1863,6 +1865,8 @@ static struct ath_buf *ath_tx_setup_buffer(struct ath_softc *sc, return bf; error: + if (dequeue) + __skb_unlink(skb, &tid->buf_q); dev_kfree_skb_any(skb); return NULL; } @@ -1893,7 +1897,7 @@ static void ath_tx_start_dma(struct ath_softc *sc, struct sk_buff *skb, */ ath_tx_send_ampdu(sc, tid, skb, txctl); } else { - bf = ath_tx_setup_buffer(sc, txctl->txq, tid, skb); + bf = ath_tx_setup_buffer(sc, txctl->txq, tid, skb, false); if (!bf) return; diff --git a/drivers/net/wireless/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/brcm80211/brcmfmac/usb.c index c5a34ffe645..a299d42da8e 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/usb.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/usb.c @@ -28,6 +28,7 @@ #include <linux/uaccess.h> #include <linux/firmware.h> #include <linux/usb.h> +#include <linux/vmalloc.h> #include <net/cfg80211.h> #include <defs.h> @@ -1239,7 +1240,7 @@ static int brcmf_usb_get_fw(struct brcmf_usbdev_info *devinfo) return -EINVAL; } - devinfo->image = kmalloc(fw->size, GFP_ATOMIC); /* plus nvram */ + devinfo->image = vmalloc(fw->size); /* plus nvram */ if (!devinfo->image) return -ENOMEM; @@ -1603,7 +1604,7 @@ static struct usb_driver brcmf_usbdrvr = { void brcmf_usb_exit(void) { usb_deregister(&brcmf_usbdrvr); - kfree(g_image.data); + vfree(g_image.data); g_image.data = NULL; g_image.len = 0; } diff --git a/drivers/net/wireless/iwlwifi/Kconfig b/drivers/net/wireless/iwlwifi/Kconfig index db6c6e52802..2463c062643 100644 --- a/drivers/net/wireless/iwlwifi/Kconfig +++ b/drivers/net/wireless/iwlwifi/Kconfig @@ -137,11 +137,3 @@ config IWLWIFI_EXPERIMENTAL_MFP even if the microcode doesn't advertise it. Say Y only if you want to experiment with MFP. - -config IWLWIFI_UCODE16 - bool "support uCode 16.0" - depends on IWLWIFI - help - This option enables support for uCode version 16.0. - - Say Y if you want to use 16.0 microcode. diff --git a/drivers/net/wireless/iwlwifi/Makefile b/drivers/net/wireless/iwlwifi/Makefile index 406f297a9a5..d615eacbf05 100644 --- a/drivers/net/wireless/iwlwifi/Makefile +++ b/drivers/net/wireless/iwlwifi/Makefile @@ -18,7 +18,6 @@ iwlwifi-objs += iwl-notif-wait.o iwlwifi-objs += iwl-trans-pcie.o iwl-trans-pcie-rx.o iwl-trans-pcie-tx.o -iwlwifi-$(CONFIG_IWLWIFI_UCODE16) += iwl-phy-db.o iwlwifi-$(CONFIG_IWLWIFI_DEBUGFS) += iwl-debugfs.o iwlwifi-$(CONFIG_IWLWIFI_DEVICE_TRACING) += iwl-devtrace.o iwlwifi-$(CONFIG_IWLWIFI_DEVICE_TESTMODE) += iwl-testmode.o diff --git a/drivers/net/wireless/iwlwifi/iwl-2000.c b/drivers/net/wireless/iwlwifi/iwl-2000.c index 7f793417c78..8133105ac64 100644 --- a/drivers/net/wireless/iwlwifi/iwl-2000.c +++ b/drivers/net/wireless/iwlwifi/iwl-2000.c @@ -79,7 +79,7 @@ static const struct iwl_base_params iwl2000_base_params = { .chain_noise_scale = 1000, .wd_timeout = IWL_DEF_WD_TIMEOUT, .max_event_log_size = 512, - .shadow_reg_enable = true, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ .hd_v2 = true, }; @@ -97,7 +97,7 @@ static const struct iwl_base_params iwl2030_base_params = { .chain_noise_scale = 1000, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, - .shadow_reg_enable = true, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ .hd_v2 = true, }; diff --git a/drivers/net/wireless/iwlwifi/iwl-6000.c b/drivers/net/wireless/iwlwifi/iwl-6000.c index 381b02cf339..19f7ee84ae8 100644 --- a/drivers/net/wireless/iwlwifi/iwl-6000.c +++ b/drivers/net/wireless/iwlwifi/iwl-6000.c @@ -86,7 +86,7 @@ static const struct iwl_base_params iwl6000_base_params = { .chain_noise_scale = 1000, .wd_timeout = IWL_DEF_WD_TIMEOUT, .max_event_log_size = 512, - .shadow_reg_enable = true, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ }; static const struct iwl_base_params iwl6050_base_params = { @@ -102,7 +102,7 @@ static const struct iwl_base_params iwl6050_base_params = { .chain_noise_scale = 1500, .wd_timeout = IWL_DEF_WD_TIMEOUT, .max_event_log_size = 1024, - .shadow_reg_enable = true, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ }; static const struct iwl_base_params iwl6000_g2_base_params = { @@ -118,7 +118,7 @@ static const struct iwl_base_params iwl6000_g2_base_params = { .chain_noise_scale = 1000, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, - .shadow_reg_enable = true, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ }; static const struct iwl_ht_params iwl6000_ht_params = { diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-rs.c b/drivers/net/wireless/iwlwifi/iwl-agn-rs.c index 51e1a69ffdd..8cebd7c363f 100644 --- a/drivers/net/wireless/iwlwifi/iwl-agn-rs.c +++ b/drivers/net/wireless/iwlwifi/iwl-agn-rs.c @@ -884,6 +884,7 @@ static void rs_bt_update_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, if ((priv->bt_traffic_load != priv->last_bt_traffic_load) || (priv->bt_full_concurrent != full_concurrent)) { priv->bt_full_concurrent = full_concurrent; + priv->last_bt_traffic_load = priv->bt_traffic_load; /* Update uCode's rate table. */ tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-sta.c b/drivers/net/wireless/iwlwifi/iwl-agn-sta.c index b31584e87bc..aea07aab3c9 100644 --- a/drivers/net/wireless/iwlwifi/iwl-agn-sta.c +++ b/drivers/net/wireless/iwlwifi/iwl-agn-sta.c @@ -772,7 +772,7 @@ void iwl_restore_stations(struct iwl_priv *priv, struct iwl_rxon_context *ctx) ~IWL_STA_DRIVER_ACTIVE; priv->stations[i].used &= ~IWL_STA_UCODE_INPROGRESS; - spin_unlock_bh(&priv->sta_lock); + continue; } /* * Rate scaling has already been initialized, send diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c index 3c72bad0ae5..d742900969e 100644 --- a/drivers/net/wireless/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/iwlwifi/iwl-drv.c @@ -657,17 +657,17 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv, return -EINVAL; } -static int alloc_pci_desc(struct iwl_drv *drv, - struct iwl_firmware_pieces *pieces, - enum iwl_ucode_type type) +static int iwl_alloc_ucode(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type) { int i; for (i = 0; i < IWL_UCODE_SECTION_MAX && get_sec_size(pieces, type, i); i++) if (iwl_alloc_fw_desc(drv, &(drv->fw.img[type].sec[i]), - get_sec(pieces, type, i))) - return -1; + get_sec(pieces, type, i))) + return -ENOMEM; return 0; } @@ -825,8 +825,8 @@ static void iwl_ucode_callback(const struct firmware *ucode_raw, void *context) * 1) unmodified from disk * 2) backup cache for save/restore during power-downs */ for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) - if (alloc_pci_desc(drv, &pieces, i)) - goto err_pci_alloc; + if (iwl_alloc_ucode(drv, &pieces, i)) + goto out_free_fw; /* Now that we can no longer fail, copy information */ @@ -866,7 +866,7 @@ static void iwl_ucode_callback(const struct firmware *ucode_raw, void *context) drv->op_mode = iwl_dvm_ops.start(drv->trans, drv->cfg, &drv->fw); if (!drv->op_mode) - goto out_unbind; + goto out_free_fw; return; @@ -877,7 +877,7 @@ static void iwl_ucode_callback(const struct firmware *ucode_raw, void *context) goto out_unbind; return; - err_pci_alloc: + out_free_fw: IWL_ERR(drv, "failed to allocate pci memory\n"); iwl_dealloc_ucode(drv); release_firmware(ucode_raw); diff --git a/drivers/net/wireless/iwlwifi/iwl-phy-db.c b/drivers/net/wireless/iwlwifi/iwl-phy-db.c deleted file mode 100644 index f166955340f..00000000000 --- a/drivers/net/wireless/iwlwifi/iwl-phy-db.c +++ /dev/null @@ -1,288 +0,0 @@ -/****************************************************************************** - * - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * GPL LICENSE SUMMARY - * - * Copyright(c) 2007 - 2012 Intel Corporation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, - * USA - * - * The full GNU General Public License is included in this distribution - * in the file called LICENSE.GPL. - * - * Contact Information: - * Intel Linux Wireless <ilw@linux.intel.com> - * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 - * - * BSD LICENSE - * - * Copyright(c) 2005 - 2012 Intel Corporation. All rights reserved. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name Intel Corporation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - *****************************************************************************/ - -#include <linux/slab.h> -#include <linux/string.h> - -#include "iwl-debug.h" -#include "iwl-dev.h" - -#include "iwl-phy-db.h" - -#define CHANNEL_NUM_SIZE 4 /* num of channels in calib_ch size */ - -struct iwl_phy_db *iwl_phy_db_init(struct device *dev) -{ - struct iwl_phy_db *phy_db = kzalloc(sizeof(struct iwl_phy_db), - GFP_KERNEL); - - if (!phy_db) - return phy_db; - - phy_db->dev = dev; - - /* TODO: add default values of the phy db. */ - return phy_db; -} - -/* - * get phy db section: returns a pointer to a phy db section specified by - * type and channel group id. - */ -static struct iwl_phy_db_entry * -iwl_phy_db_get_section(struct iwl_phy_db *phy_db, - enum iwl_phy_db_section_type type, - u16 chg_id) -{ - if (!phy_db || type < 0 || type >= IWL_PHY_DB_MAX) - return NULL; - - switch (type) { - case IWL_PHY_DB_CFG: - return &phy_db->cfg; - case IWL_PHY_DB_CALIB_NCH: - return &phy_db->calib_nch; - case IWL_PHY_DB_CALIB_CH: - return &phy_db->calib_ch; - case IWL_PHY_DB_CALIB_CHG_PAPD: - if (chg_id < 0 || chg_id >= IWL_NUM_PAPD_CH_GROUPS) - return NULL; - return &phy_db->calib_ch_group_papd[chg_id]; - case IWL_PHY_DB_CALIB_CHG_TXP: - if (chg_id < 0 || chg_id >= IWL_NUM_TXP_CH_GROUPS) - return NULL; - return &phy_db->calib_ch_group_txp[chg_id]; - default: - return NULL; - } - return NULL; -} - -static void iwl_phy_db_free_section(struct iwl_phy_db *phy_db, - enum iwl_phy_db_section_type type, - u16 chg_id) -{ - struct iwl_phy_db_entry *entry = - iwl_phy_db_get_section(phy_db, type, chg_id); - if (!entry) - return; - - kfree(entry->data); - entry->data = NULL; - entry->size = 0; -} - -void iwl_phy_db_free(struct iwl_phy_db *phy_db) -{ - int i; - - if (!phy_db) - return; - - iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CFG, 0); - iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_NCH, 0); - iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CH, 0); - for (i = 0; i < IWL_NUM_PAPD_CH_GROUPS; i++) - iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_PAPD, i); - for (i = 0; i < IWL_NUM_TXP_CH_GROUPS; i++) - iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_TXP, i); - - kfree(phy_db); -} - -int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, - enum iwl_phy_db_section_type type, u8 *data, - u16 size, gfp_t alloc_ctx) -{ - struct iwl_phy_db_entry *entry; - u16 chg_id = 0; - - if (!phy_db) - return -EINVAL; - - if (type == IWL_PHY_DB_CALIB_CHG_PAPD || - type == IWL_PHY_DB_CALIB_CHG_TXP) - chg_id = le16_to_cpup((__le16 *)data); - - entry = iwl_phy_db_get_section(phy_db, type, chg_id); - if (!entry) - return -EINVAL; - - kfree(entry->data); - entry->data = kmemdup(data, size, alloc_ctx); - if (!entry->data) { - entry->size = 0; - return -ENOMEM; - } - - entry->size = size; - - if (type == IWL_PHY_DB_CALIB_CH) { - phy_db->channel_num = le32_to_cpup((__le32 *)data); - phy_db->channel_size = - (size - CHANNEL_NUM_SIZE) / phy_db->channel_num; - } - - return 0; -} - -static int is_valid_channel(u16 ch_id) -{ - if (ch_id <= 14 || - (36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) || - (100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) || - (145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1)) - return 1; - return 0; -} - -static u8 ch_id_to_ch_index(u16 ch_id) -{ - if (WARN_ON(!is_valid_channel(ch_id))) - return 0xff; - - if (ch_id <= 14) - return ch_id - 1; - if (ch_id <= 64) - return (ch_id + 20) / 4; - if (ch_id <= 140) - return (ch_id - 12) / 4; - return (ch_id - 13) / 4; -} - - -static u16 channel_id_to_papd(u16 ch_id) -{ - if (WARN_ON(!is_valid_channel(ch_id))) - return 0xff; - - if (1 <= ch_id && ch_id <= 14) - return 0; - if (36 <= ch_id && ch_id <= 64) - return 1; - if (100 <= ch_id && ch_id <= 140) - return 2; - return 3; -} - -static u16 channel_id_to_txp(struct iwl_phy_db *phy_db, u16 ch_id) -{ - struct iwl_phy_db_chg_txp *txp_chg; - int i; - u8 ch_index = ch_id_to_ch_index(ch_id); - if (ch_index == 0xff) - return 0xff; - - for (i = 0; i < IWL_NUM_TXP_CH_GROUPS; i++) { - txp_chg = (void *)phy_db->calib_ch_group_txp[i].data; - if (!txp_chg) - return 0xff; - /* - * Looking for the first channel group that its max channel is - * higher then wanted channel. - */ - if (le16_to_cpu(txp_chg->max_channel_idx) >= ch_index) - return i; - } - return 0xff; -} - -int iwl_phy_db_get_section_data(struct iwl_phy_db *phy_db, - enum iwl_phy_db_section_type type, u8 **data, - u16 *size, u16 ch_id) -{ - struct iwl_phy_db_entry *entry; - u32 channel_num; - u32 channel_size; - u16 ch_group_id = 0; - u16 index; - - if (!phy_db) - return -EINVAL; - - /* find wanted channel group */ - if (type == IWL_PHY_DB_CALIB_CHG_PAPD) - ch_group_id = channel_id_to_papd(ch_id); - else if (type == IWL_PHY_DB_CALIB_CHG_TXP) - ch_group_id = channel_id_to_txp(phy_db, ch_id); - - entry = iwl_phy_db_get_section(phy_db, type, ch_group_id); - if (!entry) - return -EINVAL; - - if (type == IWL_PHY_DB_CALIB_CH) { - index = ch_id_to_ch_index(ch_id); - channel_num = phy_db->channel_num; - channel_size = phy_db->channel_size; - if (index >= channel_num) { - IWL_ERR(phy_db, "Wrong channel number %d", ch_id); - return -EINVAL; - } - *data = entry->data + CHANNEL_NUM_SIZE + index * channel_size; - *size = channel_size; - } else { - *data = entry->data; - *size = entry->size; - } - return 0; -} diff --git a/drivers/net/wireless/iwlwifi/iwl-phy-db.h b/drivers/net/wireless/iwlwifi/iwl-phy-db.h deleted file mode 100644 index c34c6a9303a..00000000000 --- a/drivers/net/wireless/iwlwifi/iwl-phy-db.h +++ /dev/null @@ -1,129 +0,0 @@ -/****************************************************************************** - * - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * GPL LICENSE SUMMARY - * - * Copyright(c) 2007 - 2012 Intel Corporation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, - * USA - * - * The full GNU General Public License is included in this distribution - * in the file called LICENSE.GPL. - * - * Contact Information: - * Intel Linux Wireless <ilw@linux.intel.com> - * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 - * - * BSD LICENSE - * - * Copyright(c) 2005 - 2012 Intel Corporation. All rights reserved. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name Intel Corporation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - *****************************************************************************/ - -#ifndef __IWL_PHYDB_H__ -#define __IWL_PHYDB_H__ - -#include <linux/types.h> - -#define IWL_NUM_PAPD_CH_GROUPS 4 -#define IWL_NUM_TXP_CH_GROUPS 8 - -struct iwl_phy_db_entry { - u16 size; - u8 *data; -}; - -struct iwl_shared; - -/** - * struct iwl_phy_db - stores phy configuration and calibration data. - * - * @cfg: phy configuration. - * @calib_nch: non channel specific calibration data. - * @calib_ch: channel specific calibration data. - * @calib_ch_group_papd: calibration data related to papd channel group. - * @calib_ch_group_txp: calibration data related to tx power chanel group. - */ -struct iwl_phy_db { - struct iwl_phy_db_entry cfg; - struct iwl_phy_db_entry calib_nch; - struct iwl_phy_db_entry calib_ch; - struct iwl_phy_db_entry calib_ch_group_papd[IWL_NUM_PAPD_CH_GROUPS]; - struct iwl_phy_db_entry calib_ch_group_txp[IWL_NUM_TXP_CH_GROUPS]; - - u32 channel_num; - u32 channel_size; - - /* for an access to the logger */ - struct device *dev; -}; - -enum iwl_phy_db_section_type { - IWL_PHY_DB_CFG = 1, - IWL_PHY_DB_CALIB_NCH, - IWL_PHY_DB_CALIB_CH, - IWL_PHY_DB_CALIB_CHG_PAPD, - IWL_PHY_DB_CALIB_CHG_TXP, - IWL_PHY_DB_MAX -}; - -/* for parsing of tx power channel group data that comes from the firmware*/ -struct iwl_phy_db_chg_txp { - __le32 space; - __le16 max_channel_idx; -} __packed; - -struct iwl_phy_db *iwl_phy_db_init(struct device *dev); - -void iwl_phy_db_free(struct iwl_phy_db *phy_db); - -int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, - enum iwl_phy_db_section_type type, u8 *data, - u16 size, gfp_t alloc_ctx); - -int iwl_phy_db_get_section_data(struct iwl_phy_db *phy_db, - enum iwl_phy_db_section_type type, u8 **data, - u16 *size, u16 ch_id); - -#endif /* __IWL_PHYDB_H__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-trans-pcie-int.h b/drivers/net/wireless/iwlwifi/iwl-trans-pcie-int.h index 6213c05a4b5..e959207c630 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans-pcie-int.h +++ b/drivers/net/wireless/iwlwifi/iwl-trans-pcie-int.h @@ -347,7 +347,7 @@ void iwl_trans_tx_queue_set_status(struct iwl_trans *trans, void iwl_trans_pcie_tx_agg_setup(struct iwl_trans *trans, int queue, int fifo, int sta_id, int tid, int frame_limit, u16 ssn); void iwlagn_txq_free_tfd(struct iwl_trans *trans, struct iwl_tx_queue *txq, - int index, enum dma_data_direction dma_dir); + enum dma_data_direction dma_dir); int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, struct sk_buff_head *skbs); int iwl_queue_space(const struct iwl_queue *q); diff --git a/drivers/net/wireless/iwlwifi/iwl-trans-pcie-tx.c b/drivers/net/wireless/iwlwifi/iwl-trans-pcie-tx.c index 21a8a672fbb..a8750238ee0 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans-pcie-tx.c +++ b/drivers/net/wireless/iwlwifi/iwl-trans-pcie-tx.c @@ -204,33 +204,39 @@ static void iwlagn_unmap_tfd(struct iwl_trans *trans, struct iwl_cmd_meta *meta, for (i = 1; i < num_tbs; i++) dma_unmap_single(trans->dev, iwl_tfd_tb_get_addr(tfd, i), iwl_tfd_tb_get_len(tfd, i), dma_dir); + + tfd->num_tbs = 0; } /** * iwlagn_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr] * @trans - transport private data * @txq - tx queue - * @index - the index of the TFD to be freed - *@dma_dir - the direction of the DMA mapping + * @dma_dir - the direction of the DMA mapping * * Does NOT advance any TFD circular buffer read/write indexes * Does NOT free the TFD itself (which is within circular buffer) */ void iwlagn_txq_free_tfd(struct iwl_trans *trans, struct iwl_tx_queue *txq, - int index, enum dma_data_direction dma_dir) + enum dma_data_direction dma_dir) { struct iwl_tfd *tfd_tmp = txq->tfds; + /* rd_ptr is bounded by n_bd and idx is bounded by n_window */ + int rd_ptr = txq->q.read_ptr; + int idx = get_cmd_index(&txq->q, rd_ptr); + lockdep_assert_held(&txq->lock); - iwlagn_unmap_tfd(trans, &txq->entries[index].meta, - &tfd_tmp[index], dma_dir); + /* We have only q->n_window txq->entries, but we use q->n_bd tfds */ + iwlagn_unmap_tfd(trans, &txq->entries[idx].meta, + &tfd_tmp[rd_ptr], dma_dir); /* free SKB */ if (txq->entries) { struct sk_buff *skb; - skb = txq->entries[index].skb; + skb = txq->entries[idx].skb; /* Can be called from irqs-disabled context * If skb is not NULL, it means that the whole queue is being @@ -238,7 +244,7 @@ void iwlagn_txq_free_tfd(struct iwl_trans *trans, struct iwl_tx_queue *txq, */ if (skb) { iwl_op_mode_free_skb(trans->op_mode, skb); - txq->entries[index].skb = NULL; + txq->entries[idx].skb = NULL; } } } @@ -973,7 +979,7 @@ int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, iwlagn_txq_inval_byte_cnt_tbl(trans, txq); - iwlagn_txq_free_tfd(trans, txq, txq->q.read_ptr, DMA_TO_DEVICE); + iwlagn_txq_free_tfd(trans, txq, DMA_TO_DEVICE); freed++; } diff --git a/drivers/net/wireless/iwlwifi/iwl-trans-pcie.c b/drivers/net/wireless/iwlwifi/iwl-trans-pcie.c index 2e57161854b..ec6fb395b84 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans-pcie.c +++ b/drivers/net/wireless/iwlwifi/iwl-trans-pcie.c @@ -435,9 +435,7 @@ static void iwl_tx_queue_unmap(struct iwl_trans *trans, int txq_id) spin_lock_bh(&txq->lock); while (q->write_ptr != q->read_ptr) { - /* The read_ptr needs to bound by q->n_window */ - iwlagn_txq_free_tfd(trans, txq, get_cmd_index(q, q->read_ptr), - dma_dir); + iwlagn_txq_free_tfd(trans, txq, dma_dir); q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd); } spin_unlock_bh(&txq->lock); diff --git a/drivers/net/wireless/ti/wl1251/sdio.c b/drivers/net/wireless/ti/wl1251/sdio.c index 1b851f650e0..e2750a12c6f 100644 --- a/drivers/net/wireless/ti/wl1251/sdio.c +++ b/drivers/net/wireless/ti/wl1251/sdio.c @@ -260,6 +260,7 @@ static int wl1251_sdio_probe(struct sdio_func *func, } if (wl->irq) { + irq_set_status_flags(wl->irq, IRQ_NOAUTOEN); ret = request_irq(wl->irq, wl1251_line_irq, 0, "wl1251", wl); if (ret < 0) { wl1251_error("request_irq() failed: %d", ret); @@ -267,7 +268,6 @@ static int wl1251_sdio_probe(struct sdio_func *func, } irq_set_irq_type(wl->irq, IRQ_TYPE_EDGE_RISING); - disable_irq(wl->irq); wl1251_sdio_ops.enable_irq = wl1251_enable_line_irq; wl1251_sdio_ops.disable_irq = wl1251_disable_line_irq; diff --git a/drivers/net/wireless/ti/wl1251/spi.c b/drivers/net/wireless/ti/wl1251/spi.c index 6248c354fc5..87f6305bda2 100644 --- a/drivers/net/wireless/ti/wl1251/spi.c +++ b/drivers/net/wireless/ti/wl1251/spi.c @@ -281,6 +281,7 @@ static int __devinit wl1251_spi_probe(struct spi_device *spi) wl->use_eeprom = pdata->use_eeprom; + irq_set_status_flags(wl->irq, IRQ_NOAUTOEN); ret = request_irq(wl->irq, wl1251_irq, 0, DRIVER_NAME, wl); if (ret < 0) { wl1251_error("request_irq() failed: %d", ret); @@ -289,8 +290,6 @@ static int __devinit wl1251_spi_probe(struct spi_device *spi) irq_set_irq_type(wl->irq, IRQ_TYPE_EDGE_RISING); - disable_irq(wl->irq); - ret = wl1251_init_ieee80211(wl); if (ret) goto out_irq; diff --git a/drivers/net/wireless/ti/wlcore/acx.c b/drivers/net/wireless/ti/wlcore/acx.c index 509aa881d79..f3d6fa50826 100644 --- a/drivers/net/wireless/ti/wlcore/acx.c +++ b/drivers/net/wireless/ti/wlcore/acx.c @@ -1715,6 +1715,7 @@ out: } +#ifdef CONFIG_PM /* Set the global behaviour of RX filters - On/Off + default action */ int wl1271_acx_default_rx_filter_enable(struct wl1271 *wl, bool enable, enum rx_filter_action action) @@ -1794,3 +1795,4 @@ out: kfree(acx); return ret; } +#endif /* CONFIG_PM */ diff --git a/drivers/net/wireless/ti/wlcore/acx.h b/drivers/net/wireless/ti/wlcore/acx.h index 8106b2ebfe6..e6a74869a5f 100644 --- a/drivers/net/wireless/ti/wlcore/acx.h +++ b/drivers/net/wireless/ti/wlcore/acx.h @@ -1330,9 +1330,11 @@ int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); int wl1271_acx_fm_coex(struct wl1271 *wl); int wl12xx_acx_set_rate_mgmt_params(struct wl1271 *wl); int wl12xx_acx_config_hangover(struct wl1271 *wl); + +#ifdef CONFIG_PM int wl1271_acx_default_rx_filter_enable(struct wl1271 *wl, bool enable, enum rx_filter_action action); int wl1271_acx_set_rx_filter(struct wl1271 *wl, u8 index, bool enable, struct wl12xx_rx_filter *filter); - +#endif /* CONFIG_PM */ #endif /* __WL1271_ACX_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/rx.c b/drivers/net/wireless/ti/wlcore/rx.c index 1f1d9488dfb..d6a3c6b0782 100644 --- a/drivers/net/wireless/ti/wlcore/rx.c +++ b/drivers/net/wireless/ti/wlcore/rx.c @@ -279,6 +279,7 @@ void wl12xx_rx(struct wl1271 *wl, struct wl_fw_status *status) wl12xx_rearm_rx_streaming(wl, active_hlids); } +#ifdef CONFIG_PM int wl1271_rx_filter_enable(struct wl1271 *wl, int index, bool enable, struct wl12xx_rx_filter *filter) @@ -314,3 +315,4 @@ void wl1271_rx_filter_clear_all(struct wl1271 *wl) wl1271_rx_filter_enable(wl, i, 0, NULL); } } +#endif /* CONFIG_PM */ diff --git a/drivers/net/xen-netback/netback.c b/drivers/net/xen-netback/netback.c index 2596401308a..f4a6fcaeffb 100644 --- a/drivers/net/xen-netback/netback.c +++ b/drivers/net/xen-netback/netback.c @@ -325,8 +325,7 @@ unsigned int xen_netbk_count_skb_slots(struct xenvif *vif, struct sk_buff *skb) unsigned int count; int i, copy_off; - count = DIV_ROUND_UP( - offset_in_page(skb->data)+skb_headlen(skb), PAGE_SIZE); + count = DIV_ROUND_UP(skb_headlen(skb), PAGE_SIZE); copy_off = skb_headlen(skb) % PAGE_SIZE; diff --git a/drivers/nfc/pn544_hci.c b/drivers/nfc/pn544_hci.c index 46f4a9f9f5e..281f18c2fb8 100644 --- a/drivers/nfc/pn544_hci.c +++ b/drivers/nfc/pn544_hci.c @@ -232,7 +232,7 @@ static int pn544_hci_i2c_write(struct i2c_client *client, u8 *buf, int len) static int check_crc(u8 *buf, int buflen) { - u8 len; + int len; u16 crc; len = buf[0] + 1; diff --git a/drivers/of/of_pci_irq.c b/drivers/of/of_pci_irq.c index 93125163dea..67705381321 100644 --- a/drivers/of/of_pci_irq.c +++ b/drivers/of/of_pci_irq.c @@ -15,7 +15,7 @@ * PCI tree until an device-node is found, at which point it will finish * resolving using the OF tree walking. */ -int of_irq_map_pci(struct pci_dev *pdev, struct of_irq *out_irq) +int of_irq_map_pci(const struct pci_dev *pdev, struct of_irq *out_irq) { struct device_node *dn, *ppnode; struct pci_dev *ppdev; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8f169002dc7..447e83472c0 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2370,7 +2370,7 @@ void pci_enable_acs(struct pci_dev *dev) * number is always 0 (see the Implementation Note in section 2.2.8.1 of * the PCI Express Base Specification, Revision 2.1) */ -u8 pci_swizzle_interrupt_pin(struct pci_dev *dev, u8 pin) +u8 pci_swizzle_interrupt_pin(const struct pci_dev *dev, u8 pin) { int slot; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 99dc29f2f2f..e3a3b4956f0 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,5 +1,5 @@ menuconfig POWER_SUPPLY - tristate "Power supply class support" + bool "Power supply class support" help Say Y here to enable power supply class support. This allows power supply (batteries, AC, USB) monitoring by userspace @@ -77,7 +77,7 @@ config BATTERY_DS2780 Say Y here to enable support for batteries with ds2780 chip. config BATTERY_DS2781 - tristate "2781 battery driver" + tristate "DS2781 battery driver" depends on HAS_IOMEM select W1 select W1_SLAVE_DS2781 @@ -181,14 +181,15 @@ config BATTERY_MAX17040 to operate with a single lithium cell config BATTERY_MAX17042 - tristate "Maxim MAX17042/8997/8966 Fuel Gauge" + tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge" depends on I2C help MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries in handheld and portable equipment. The MAX17042 is configured to operate with a single lithium cell. MAX8997 and MAX8966 are multi-function devices that include fuel gauages that are compatible - with MAX17042. + with MAX17042. This driver also supports max17047/50 chips which are + improved version of max17042. config BATTERY_Z2 tristate "Z2 battery driver" @@ -291,6 +292,7 @@ config CHARGER_MAX8998 config CHARGER_SMB347 tristate "Summit Microelectronics SMB347 Battery Charger" depends on I2C + select REGMAP_I2C help Say Y to include support for Summit Microelectronics SMB347 Battery Charger. diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index d8bb99394ac..bba3ccac72f 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -964,10 +964,15 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev) { int irq, i, ret = 0; u8 val; - struct abx500_bm_plat_data *plat_data; + struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data; + struct ab8500_btemp *di; + + if (!plat_data) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } - struct ab8500_btemp *di = - kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL); + di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; @@ -977,7 +982,6 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev) di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); /* get btemp specific platform data */ - plat_data = pdev->dev.platform_data; di->pdata = plat_data->btemp; if (!di->pdata) { dev_err(di->dev, "no btemp platform data supplied\n"); diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index e2b4accbec8..d2303d0b7c7 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -2534,10 +2534,15 @@ static int __devexit ab8500_charger_remove(struct platform_device *pdev) static int __devinit ab8500_charger_probe(struct platform_device *pdev) { int irq, i, charger_status, ret = 0; - struct abx500_bm_plat_data *plat_data; + struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data; + struct ab8500_charger *di; - struct ab8500_charger *di = - kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL); + if (!plat_data) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; @@ -2550,9 +2555,7 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) spin_lock_init(&di->usb_state.usb_lock); /* get charger specific platform data */ - plat_data = pdev->dev.platform_data; di->pdata = plat_data->charger; - if (!di->pdata) { dev_err(di->dev, "no charger platform data supplied\n"); ret = -EINVAL; diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index c22f2f05657..bf022255994 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -2446,10 +2446,15 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) { int i, irq; int ret = 0; - struct abx500_bm_plat_data *plat_data; + struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data; + struct ab8500_fg *di; + + if (!plat_data) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } - struct ab8500_fg *di = - kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL); + di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; @@ -2461,7 +2466,6 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); /* get fg specific platform data */ - plat_data = pdev->dev.platform_data; di->pdata = plat_data->fg; if (!di->pdata) { dev_err(di->dev, "no fg platform data supplied\n"); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 9eca9f1ff0e..86935ec1895 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -23,6 +23,16 @@ #include <linux/power/charger-manager.h> #include <linux/regulator/consumer.h> +static const char * const default_event_names[] = { + [CM_EVENT_UNKNOWN] = "Unknown", + [CM_EVENT_BATT_FULL] = "Battery Full", + [CM_EVENT_BATT_IN] = "Battery Inserted", + [CM_EVENT_BATT_OUT] = "Battery Pulled Out", + [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", + [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", + [CM_EVENT_OTHERS] = "Other battery events" +}; + /* * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for * delayed works so that we can run delayed works with CM_JIFFIES_SMALL @@ -57,6 +67,12 @@ static bool cm_suspended; static bool cm_rtc_set; static unsigned long cm_suspend_duration_ms; +/* About normal (not suspended) monitoring */ +static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */ +static unsigned long next_polling; /* Next appointed polling time */ +static struct workqueue_struct *cm_wq; /* init at driver add */ +static struct delayed_work cm_monitor_work; /* init at driver add */ + /* Global charger-manager description */ static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ @@ -71,6 +87,11 @@ static bool is_batt_present(struct charger_manager *cm) int i, ret; switch (cm->desc->battery_present) { + case CM_BATTERY_PRESENT: + present = true; + break; + case CM_NO_BATTERY: + break; case CM_FUEL_GAUGE: ret = cm->fuel_gauge->get_property(cm->fuel_gauge, POWER_SUPPLY_PROP_PRESENT, &val); @@ -279,6 +300,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) } /** + * try_charger_restart - Restart charging. + * @cm: the Charger Manager representing the battery. + * + * Restart charging by turning off and on the charger. + */ +static int try_charger_restart(struct charger_manager *cm) +{ + int err; + + if (cm->emergency_stop) + return -EAGAIN; + + err = try_charger_enable(cm, false); + if (err) + return err; + + return try_charger_enable(cm, true); +} + +/** * uevent_notify - Let users know something has changed. * @cm: the Charger Manager representing the battery. * @event: the event string. @@ -334,6 +375,46 @@ static void uevent_notify(struct charger_manager *cm, const char *event) } /** + * fullbatt_vchk - Check voltage drop some times after "FULL" event. + * @work: the work_struct appointing the function + * + * If a user has designated "fullbatt_vchkdrop_ms/uV" values with + * charger_desc, Charger Manager checks voltage drop after the battery + * "FULL" event. It checks whether the voltage has dropped more than + * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. + */ +static void fullbatt_vchk(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct charger_manager *cm = container_of(dwork, + struct charger_manager, fullbatt_vchk_work); + struct charger_desc *desc = cm->desc; + int batt_uV, err, diff; + + /* remove the appointment for fullbatt_vchk */ + cm->fullbatt_vchk_jiffies_at = 0; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + return; + + err = get_batt_uV(cm, &batt_uV); + if (err) { + dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err); + return; + } + + diff = cm->fullbatt_vchk_uV; + diff -= batt_uV; + + dev_dbg(cm->dev, "VBATT dropped %duV after full-batt.\n", diff); + + if (diff > desc->fullbatt_vchkdrop_uV) { + try_charger_restart(cm); + uevent_notify(cm, "Recharge"); + } +} + +/** * _cm_monitor - Monitor the temperature and return true for exceptions. * @cm: the Charger Manager representing the battery. * @@ -392,6 +473,131 @@ static bool cm_monitor(void) return stop; } +/** + * _setup_polling - Setup the next instance of polling. + * @work: work_struct of the function _setup_polling. + */ +static void _setup_polling(struct work_struct *work) +{ + unsigned long min = ULONG_MAX; + struct charger_manager *cm; + bool keep_polling = false; + unsigned long _next_polling; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + if (is_polling_required(cm) && cm->desc->polling_interval_ms) { + keep_polling = true; + + if (min > cm->desc->polling_interval_ms) + min = cm->desc->polling_interval_ms; + } + } + + polling_jiffy = msecs_to_jiffies(min); + if (polling_jiffy <= CM_JIFFIES_SMALL) + polling_jiffy = CM_JIFFIES_SMALL + 1; + + if (!keep_polling) + polling_jiffy = ULONG_MAX; + if (polling_jiffy == ULONG_MAX) + goto out; + + WARN(cm_wq == NULL, "charger-manager: workqueue not initialized" + ". try it later. %s\n", __func__); + + _next_polling = jiffies + polling_jiffy; + + if (!delayed_work_pending(&cm_monitor_work) || + (delayed_work_pending(&cm_monitor_work) && + time_after(next_polling, _next_polling))) { + cancel_delayed_work_sync(&cm_monitor_work); + next_polling = jiffies + polling_jiffy; + queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy); + } + +out: + mutex_unlock(&cm_list_mtx); +} +static DECLARE_WORK(setup_polling, _setup_polling); + +/** + * cm_monitor_poller - The Monitor / Poller. + * @work: work_struct of the function cm_monitor_poller + * + * During non-suspended state, cm_monitor_poller is used to poll and monitor + * the batteries. + */ +static void cm_monitor_poller(struct work_struct *work) +{ + cm_monitor(); + schedule_work(&setup_polling); +} + +/** + * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL + * @cm: the Charger Manager representing the battery. + */ +static void fullbatt_handler(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + + if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) + goto out; + + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (delayed_work_pending(&cm->fullbatt_vchk_work)) + cancel_delayed_work(&cm->fullbatt_vchk_work); + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(desc->fullbatt_vchkdrop_ms)); + cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies( + desc->fullbatt_vchkdrop_ms); + + if (cm->fullbatt_vchk_jiffies_at == 0) + cm->fullbatt_vchk_jiffies_at = 1; + +out: + dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged.\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); +} + +/** + * battout_handler - Event handler for CM_EVENT_BATT_OUT + * @cm: the Charger Manager representing the battery. + */ +static void battout_handler(struct charger_manager *cm) +{ + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (!is_batt_present(cm)) { + dev_emerg(cm->dev, "Battery Pulled Out!\n"); + uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]); + } else { + uevent_notify(cm, "Battery Reinserted?"); + } +} + +/** + * misc_event_handler - Handler for other evnets + * @cm: the Charger Manager representing the battery. + * @type: the Charger Manager representing the battery. + */ +static void misc_event_handler(struct charger_manager *cm, + enum cm_event_types type) +{ + if (cm_suspended) + device_set_wakeup_capable(cm->dev, true); + + if (!delayed_work_pending(&cm_monitor_work) && + is_polling_required(cm) && cm->desc->polling_interval_ms) + schedule_work(&setup_polling); + uevent_notify(cm, default_event_names[type]); +} + static int charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -613,6 +819,21 @@ static bool cm_setup_timer(void) mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { + unsigned int fbchk_ms = 0; + + /* fullbatt_vchk is required. setup timer for that */ + if (cm->fullbatt_vchk_jiffies_at) { + fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at + - jiffies); + if (time_is_before_eq_jiffies( + cm->fullbatt_vchk_jiffies_at) || + msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { + fullbatt_vchk(&cm->fullbatt_vchk_work.work); + fbchk_ms = 0; + } + } + CM_MIN_VALID(wakeup_ms, fbchk_ms); + /* Skip if polling is not required for this CM */ if (!is_polling_required(cm) && !cm->emergency_stop) continue; @@ -672,6 +893,23 @@ static bool cm_setup_timer(void) return false; } +static void _cm_fbchk_in_suspend(struct charger_manager *cm) +{ + unsigned long jiffy_now = jiffies; + + if (!cm->fullbatt_vchk_jiffies_at) + return; + + if (g_desc && g_desc->assume_timer_stops_in_suspend) + jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms); + + /* Execute now if it's going to be executed not too long after */ + jiffy_now += CM_JIFFIES_SMALL; + + if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) + fullbatt_vchk(&cm->fullbatt_vchk_work.work); +} + /** * cm_suspend_again - Determine whether suspend again or not * @@ -693,6 +931,8 @@ bool cm_suspend_again(void) ret = true; mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { + _cm_fbchk_in_suspend(cm); + if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || cm->status_save_batt != is_batt_present(cm)) { ret = false; @@ -796,6 +1036,21 @@ static int charger_manager_probe(struct platform_device *pdev) memcpy(cm->desc, desc, sizeof(struct charger_desc)); cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ + /* + * The following two do not need to be errors. + * Users may intentionally ignore those two features. + */ + if (desc->fullbatt_uV == 0) { + dev_info(&pdev->dev, "Ignoring full-battery voltage threshold" + " as it is not supplied."); + } + if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { + dev_info(&pdev->dev, "Disabling full-battery voltage drop " + "checking mechanism as it is not supplied."); + desc->fullbatt_vchkdrop_ms = 0; + desc->fullbatt_vchkdrop_uV = 0; + } + if (!desc->charger_regulators || desc->num_charger_regulators < 1) { ret = -EINVAL; dev_err(&pdev->dev, "charger_regulators undefined.\n"); @@ -903,6 +1158,8 @@ static int charger_manager_probe(struct platform_device *pdev) cm->charger_psy.num_properties++; } + INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); + ret = power_supply_register(NULL, &cm->charger_psy); if (ret) { dev_err(&pdev->dev, "Cannot register charger-manager with" @@ -928,6 +1185,15 @@ static int charger_manager_probe(struct platform_device *pdev) list_add(&cm->entry, &cm_list); mutex_unlock(&cm_list_mtx); + /* + * Charger-manager is capable of waking up the systme from sleep + * when event is happend through cm_notify_event() + */ + device_init_wakeup(&pdev->dev, true); + device_set_wakeup_capable(&pdev->dev, false); + + schedule_work(&setup_polling); + return 0; err_chg_enable: @@ -958,9 +1224,17 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) list_del(&cm->entry); mutex_unlock(&cm_list_mtx); + if (work_pending(&setup_polling)) + cancel_work_sync(&setup_polling); + if (delayed_work_pending(&cm_monitor_work)) + cancel_delayed_work_sync(&cm_monitor_work); + regulator_bulk_free(desc->num_charger_regulators, desc->charger_regulators); power_supply_unregister(&cm->charger_psy); + + try_charger_enable(cm, false); + kfree(cm->charger_psy.properties); kfree(cm->charger_stat); kfree(cm->desc); @@ -975,6 +1249,18 @@ static const struct platform_device_id charger_manager_id[] = { }; MODULE_DEVICE_TABLE(platform, charger_manager_id); +static int cm_suspend_noirq(struct device *dev) +{ + int ret = 0; + + if (device_may_wakeup(dev)) { + device_set_wakeup_capable(dev, false); + ret = -EAGAIN; + } + + return ret; +} + static int cm_suspend_prepare(struct device *dev) { struct charger_manager *cm = dev_get_drvdata(dev); @@ -1000,6 +1286,8 @@ static int cm_suspend_prepare(struct device *dev) cm_suspended = true; } + if (delayed_work_pending(&cm->fullbatt_vchk_work)) + cancel_delayed_work(&cm->fullbatt_vchk_work); cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); cm->status_save_batt = is_batt_present(cm); @@ -1027,11 +1315,40 @@ static void cm_suspend_complete(struct device *dev) cm_rtc_set = false; } + /* Re-enqueue delayed work (fullbatt_vchk_work) */ + if (cm->fullbatt_vchk_jiffies_at) { + unsigned long delay = 0; + unsigned long now = jiffies + CM_JIFFIES_SMALL; + + if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) { + delay = (unsigned long)((long)now + - (long)(cm->fullbatt_vchk_jiffies_at)); + delay = jiffies_to_msecs(delay); + } else { + delay = 0; + } + + /* + * Account for cm_suspend_duration_ms if + * assume_timer_stops_in_suspend is active + */ + if (g_desc && g_desc->assume_timer_stops_in_suspend) { + if (delay > cm_suspend_duration_ms) + delay -= cm_suspend_duration_ms; + else + delay = 0; + } + + queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, + msecs_to_jiffies(delay)); + } + device_set_wakeup_capable(cm->dev, false); uevent_notify(cm, NULL); } static const struct dev_pm_ops charger_manager_pm = { .prepare = cm_suspend_prepare, + .suspend_noirq = cm_suspend_noirq, .complete = cm_suspend_complete, }; @@ -1048,16 +1365,91 @@ static struct platform_driver charger_manager_driver = { static int __init charger_manager_init(void) { + cm_wq = create_freezable_workqueue("charger_manager"); + INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); + return platform_driver_register(&charger_manager_driver); } late_initcall(charger_manager_init); static void __exit charger_manager_cleanup(void) { + destroy_workqueue(cm_wq); + cm_wq = NULL; + platform_driver_unregister(&charger_manager_driver); } module_exit(charger_manager_cleanup); +/** + * find_power_supply - find the associated power_supply of charger + * @cm: the Charger Manager representing the battery + * @psy: pointer to instance of charger's power_supply + */ +static bool find_power_supply(struct charger_manager *cm, + struct power_supply *psy) +{ + int i; + bool found = false; + + for (i = 0; cm->charger_stat[i]; i++) { + if (psy == cm->charger_stat[i]) { + found = true; + break; + } + } + + return found; +} + +/** + * cm_notify_event - charger driver notify Charger Manager of charger event + * @psy: pointer to instance of charger's power_supply + * @type: type of charger event + * @msg: optional message passed to uevent_notify fuction + */ +void cm_notify_event(struct power_supply *psy, enum cm_event_types type, + char *msg) +{ + struct charger_manager *cm; + bool found_power_supply = false; + + if (psy == NULL) + return; + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + found_power_supply = find_power_supply(cm, psy); + if (found_power_supply) + break; + } + mutex_unlock(&cm_list_mtx); + + if (!found_power_supply) + return; + + switch (type) { + case CM_EVENT_BATT_FULL: + fullbatt_handler(cm); + break; + case CM_EVENT_BATT_OUT: + battout_handler(cm); + break; + case CM_EVENT_BATT_IN: + case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: + misc_event_handler(cm, type); + break; + case CM_EVENT_UNKNOWN: + case CM_EVENT_OTHERS: + uevent_notify(cm, msg ? msg : default_event_names[type]); + break; + default: + dev_err(cm->dev, "%s type not specified.\n", __func__); + break; + } +} +EXPORT_SYMBOL_GPL(cm_notify_event); + MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); MODULE_DESCRIPTION("Charger Manager"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c index ca0d653d0a7..975684a40f1 100644 --- a/drivers/power/ds2781_battery.c +++ b/drivers/power/ds2781_battery.c @@ -643,9 +643,7 @@ static ssize_t ds2781_read_param_eeprom_bin(struct file *filp, struct power_supply *psy = to_power_supply(dev); struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - count = min_t(loff_t, count, - DS2781_EEPROM_BLOCK1_END - - DS2781_EEPROM_BLOCK1_START + 1 - off); + count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off); return ds2781_read_block(dev_info, buf, DS2781_EEPROM_BLOCK1_START + off, count); @@ -661,9 +659,7 @@ static ssize_t ds2781_write_param_eeprom_bin(struct file *filp, struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); int ret; - count = min_t(loff_t, count, - DS2781_EEPROM_BLOCK1_END - - DS2781_EEPROM_BLOCK1_START + 1 - off); + count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off); ret = ds2781_write(dev_info, buf, DS2781_EEPROM_BLOCK1_START + off, count); @@ -682,7 +678,7 @@ static struct bin_attribute ds2781_param_eeprom_bin_attr = { .name = "param_eeprom", .mode = S_IRUGO | S_IWUSR, }, - .size = DS2781_EEPROM_BLOCK1_END - DS2781_EEPROM_BLOCK1_START + 1, + .size = DS2781_PARAM_EEPROM_SIZE, .read = ds2781_read_param_eeprom_bin, .write = ds2781_write_param_eeprom_bin, }; @@ -696,9 +692,7 @@ static ssize_t ds2781_read_user_eeprom_bin(struct file *filp, struct power_supply *psy = to_power_supply(dev); struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); - count = min_t(loff_t, count, - DS2781_EEPROM_BLOCK0_END - - DS2781_EEPROM_BLOCK0_START + 1 - off); + count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off); return ds2781_read_block(dev_info, buf, DS2781_EEPROM_BLOCK0_START + off, count); @@ -715,9 +709,7 @@ static ssize_t ds2781_write_user_eeprom_bin(struct file *filp, struct ds2781_device_info *dev_info = to_ds2781_device_info(psy); int ret; - count = min_t(loff_t, count, - DS2781_EEPROM_BLOCK0_END - - DS2781_EEPROM_BLOCK0_START + 1 - off); + count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off); ret = ds2781_write(dev_info, buf, DS2781_EEPROM_BLOCK0_START + off, count); @@ -736,7 +728,7 @@ static struct bin_attribute ds2781_user_eeprom_bin_attr = { .name = "user_eeprom", .mode = S_IRUGO | S_IWUSR, }, - .size = DS2781_EEPROM_BLOCK0_END - DS2781_EEPROM_BLOCK0_START + 1, + .size = DS2781_USER_EEPROM_SIZE, .read = ds2781_read_user_eeprom_bin, .write = ds2781_write_user_eeprom_bin, }; diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index 39eb50f35f0..e5ccd297977 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c @@ -474,13 +474,13 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev) fail2: power_supply_unregister(&isp->psy); fail1: + isp1704_charger_set_power(isp, 0); usb_put_transceiver(isp->phy); fail0: kfree(isp); dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); - isp1704_charger_set_power(isp, 0); return ret; } diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 04620c2cb38..140788b309f 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -28,6 +28,7 @@ #include <linux/i2c.h> #include <linux/delay.h> #include <linux/interrupt.h> +#include <linux/pm.h> #include <linux/mod_devicetable.h> #include <linux/power_supply.h> #include <linux/power/max17042_battery.h> @@ -61,9 +62,13 @@ #define dP_ACC_100 0x1900 #define dP_ACC_200 0x3200 +#define MAX17042_IC_VERSION 0x0092 +#define MAX17047_IC_VERSION 0x00AC /* same for max17050 */ + struct max17042_chip { struct i2c_client *client; struct power_supply battery; + enum max170xx_chip_type chip_type; struct max17042_platform_data *pdata; struct work_struct work; int init_complete; @@ -105,6 +110,7 @@ static enum power_supply_property max17042_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_TEMP, @@ -150,7 +156,10 @@ static int max17042_get_property(struct power_supply *psy, val->intval *= 20000; /* Units of LSB = 20mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - ret = max17042_read_reg(chip->client, MAX17042_V_empty); + if (chip->chip_type == MAX17042) + ret = max17042_read_reg(chip->client, MAX17042_V_empty); + else + ret = max17042_read_reg(chip->client, MAX17047_V_empty); if (ret < 0) return ret; @@ -171,6 +180,13 @@ static int max17042_get_property(struct power_supply *psy, val->intval = ret * 625 / 8; break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + ret = max17042_read_reg(chip->client, MAX17042_OCVInternal); + if (ret < 0) + return ret; + + val->intval = ret * 625 / 8; + break; case POWER_SUPPLY_PROP_CAPACITY: ret = max17042_read_reg(chip->client, MAX17042_RepSOC); if (ret < 0) @@ -325,11 +341,10 @@ static inline int max17042_model_data_compare(struct max17042_chip *chip, static int max17042_init_model(struct max17042_chip *chip) { int ret; - int table_size = - sizeof(chip->pdata->config_data->cell_char_tbl)/sizeof(u16); + int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); u16 *temp_data; - temp_data = kzalloc(table_size, GFP_KERNEL); + temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); if (!temp_data) return -ENOMEM; @@ -354,12 +369,11 @@ static int max17042_init_model(struct max17042_chip *chip) static int max17042_verify_model_lock(struct max17042_chip *chip) { int i; - int table_size = - sizeof(chip->pdata->config_data->cell_char_tbl); + int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); u16 *temp_data; int ret = 0; - temp_data = kzalloc(table_size, GFP_KERNEL); + temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); if (!temp_data) return -ENOMEM; @@ -382,6 +396,9 @@ static void max17042_write_config_regs(struct max17042_chip *chip) max17042_write_reg(chip->client, MAX17042_FilterCFG, config->filter_cfg); max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg); + if (chip->chip_type == MAX17047) + max17042_write_reg(chip->client, MAX17047_FullSOCThr, + config->full_soc_thresh); } static void max17042_write_custom_regs(struct max17042_chip *chip) @@ -392,12 +409,23 @@ static void max17042_write_custom_regs(struct max17042_chip *chip) config->rcomp0); max17042_write_verify_reg(chip->client, MAX17042_TempCo, config->tcompc0); - max17042_write_reg(chip->client, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_write_verify_reg(chip->client, MAX17042_K_empty0, - config->kempty0); max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm, config->ichgt_term); + if (chip->chip_type == MAX17042) { + max17042_write_reg(chip->client, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_write_verify_reg(chip->client, MAX17042_K_empty0, + config->kempty0); + } else { + max17042_write_verify_reg(chip->client, MAX17047_QRTbl00, + config->qrtbl00); + max17042_write_verify_reg(chip->client, MAX17047_QRTbl10, + config->qrtbl10); + max17042_write_verify_reg(chip->client, MAX17047_QRTbl20, + config->qrtbl20); + max17042_write_verify_reg(chip->client, MAX17047_QRTbl30, + config->qrtbl30); + } } static void max17042_update_capacity_regs(struct max17042_chip *chip) @@ -453,6 +481,8 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip) config->design_cap); max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, config->fullcapnom); + /* Update SOC register with new SOC */ + max17042_write_reg(chip->client, MAX17042_RepSOC, vfSoc); } /* @@ -489,20 +519,28 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(client, MAX17042_FullCAP, config->fullcap); max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom); - max17042_override_por(client, MAX17042_SOC_empty, config->socempty); + if (chip->chip_type == MAX17042) + max17042_override_por(client, MAX17042_SOC_empty, + config->socempty); max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty); max17042_override_por(client, MAX17042_dQacc, config->dqacc); max17042_override_por(client, MAX17042_dPacc, config->dpacc); - max17042_override_por(client, MAX17042_V_empty, config->vempty); + if (chip->chip_type == MAX17042) + max17042_override_por(client, MAX17042_V_empty, config->vempty); + else + max17042_override_por(client, MAX17047_V_empty, config->vempty); max17042_override_por(client, MAX17042_TempNom, config->temp_nom); max17042_override_por(client, MAX17042_TempLim, config->temp_lim); max17042_override_por(client, MAX17042_FCTC, config->fctc); max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0); max17042_override_por(client, MAX17042_TempCo, config->tcompc0); - max17042_override_por(client, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_override_por(client, MAX17042_K_empty0, config->kempty0); + if (chip->chip_type) { + max17042_override_por(client, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_override_por(client, MAX17042_K_empty0, + config->kempty0); + } } static int max17042_init_chip(struct max17042_chip *chip) @@ -659,7 +697,19 @@ static int __devinit max17042_probe(struct i2c_client *client, i2c_set_clientdata(client, chip); - chip->battery.name = "max17042_battery"; + ret = max17042_read_reg(chip->client, MAX17042_DevName); + if (ret == MAX17042_IC_VERSION) { + dev_dbg(&client->dev, "chip type max17042 detected\n"); + chip->chip_type = MAX17042; + } else if (ret == MAX17047_IC_VERSION) { + dev_dbg(&client->dev, "chip type max17047/50 detected\n"); + chip->chip_type = MAX17047; + } else { + dev_err(&client->dev, "device version mismatch: %x\n", ret); + return -EIO; + } + + chip->battery.name = "max170xx_battery"; chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; chip->battery.get_property = max17042_get_property; chip->battery.properties = max17042_battery_props; @@ -683,6 +733,12 @@ static int __devinit max17042_probe(struct i2c_client *client, max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); } + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + return ret; + } + if (client->irq) { ret = request_threaded_irq(client->irq, NULL, max17042_thread_handler, @@ -693,13 +749,14 @@ static int __devinit max17042_probe(struct i2c_client *client, reg |= CONFIG_ALRT_BIT_ENBL; max17042_write_reg(client, MAX17042_CONFIG, reg); max17042_set_soc_threshold(chip, 1); - } else + } else { + client->irq = 0; dev_err(&client->dev, "%s(): cannot get IRQ\n", __func__); + } } reg = max17042_read_reg(chip->client, MAX17042_STATUS); - if (reg & STATUS_POR_BIT) { INIT_WORK(&chip->work, max17042_init_worker); schedule_work(&chip->work); @@ -707,23 +764,65 @@ static int __devinit max17042_probe(struct i2c_client *client, chip->init_complete = 1; } - ret = power_supply_register(&client->dev, &chip->battery); - if (ret) - dev_err(&client->dev, "failed: power supply register\n"); - return ret; + return 0; } static int __devexit max17042_remove(struct i2c_client *client) { struct max17042_chip *chip = i2c_get_clientdata(client); + if (client->irq) + free_irq(client->irq, chip); power_supply_unregister(&chip->battery); return 0; } +#ifdef CONFIG_PM +static int max17042_suspend(struct device *dev) +{ + struct max17042_chip *chip = dev_get_drvdata(dev); + + /* + * disable the irq and enable irq_wake + * capability to the interrupt line. + */ + if (chip->client->irq) { + disable_irq(chip->client->irq); + enable_irq_wake(chip->client->irq); + } + + return 0; +} + +static int max17042_resume(struct device *dev) +{ + struct max17042_chip *chip = dev_get_drvdata(dev); + + if (chip->client->irq) { + disable_irq_wake(chip->client->irq); + enable_irq(chip->client->irq); + /* re-program the SOC thresholds to 1% change */ + max17042_set_soc_threshold(chip, 1); + } + + return 0; +} + +static const struct dev_pm_ops max17042_pm_ops = { + .suspend = max17042_suspend, + .resume = max17042_resume, +}; + +#define MAX17042_PM_OPS (&max17042_pm_ops) +#else +#define MAX17042_PM_OPS NULL +#endif + #ifdef CONFIG_OF static const struct of_device_id max17042_dt_match[] = { { .compatible = "maxim,max17042" }, + { .compatible = "maxim,max17047" }, + { .compatible = "maxim,max17050" }, { }, }; MODULE_DEVICE_TABLE(of, max17042_dt_match); @@ -731,6 +830,8 @@ MODULE_DEVICE_TABLE(of, max17042_dt_match); static const struct i2c_device_id max17042_id[] = { { "max17042", 0 }, + { "max17047", 1 }, + { "max17050", 2 }, { } }; MODULE_DEVICE_TABLE(i2c, max17042_id); @@ -739,6 +840,7 @@ static struct i2c_driver max17042_i2c_driver = { .driver = { .name = "max17042", .of_match_table = of_match_ptr(max17042_dt_match), + .pm = MAX17042_PM_OPS, }, .probe = max17042_probe, .remove = __devexit_p(max17042_remove), diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 4368e7d6131..4150747f918 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -146,6 +146,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(voltage_min_design), POWER_SUPPLY_ATTR(voltage_now), POWER_SUPPLY_ATTR(voltage_avg), + POWER_SUPPLY_ATTR(voltage_ocv), POWER_SUPPLY_ATTR(current_max), POWER_SUPPLY_ATTR(current_now), POWER_SUPPLY_ATTR(current_avg), diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index 06b659d9179..a5b6849d412 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -89,7 +89,7 @@ static const struct chip_data { [REG_CURRENT] = SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), [REG_CAPACITY] = - SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), + SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100), [REG_REMAINING_CAPACITY] = SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), [REG_REMAINING_CAPACITY_CHARGE] = diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c index ce1694d1a36..f8eedd8a676 100644 --- a/drivers/power/smb347-charger.c +++ b/drivers/power/smb347-charger.c @@ -11,7 +11,7 @@ * published by the Free Software Foundation. */ -#include <linux/debugfs.h> +#include <linux/err.h> #include <linux/gpio.h> #include <linux/kernel.h> #include <linux/module.h> @@ -21,7 +21,7 @@ #include <linux/mutex.h> #include <linux/power_supply.h> #include <linux/power/smb347-charger.h> -#include <linux/seq_file.h> +#include <linux/regmap.h> /* * Configuration registers. These are mirrored to volatile RAM and can be @@ -39,6 +39,7 @@ #define CFG_CURRENT_LIMIT_DC_SHIFT 4 #define CFG_CURRENT_LIMIT_USB_MASK 0x0f #define CFG_FLOAT_VOLTAGE 0x03 +#define CFG_FLOAT_VOLTAGE_FLOAT_MASK 0x3f #define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0 #define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6 #define CFG_STAT 0x05 @@ -113,29 +114,31 @@ #define STAT_C_CHARGER_ERROR BIT(6) #define STAT_E 0x3f +#define SMB347_MAX_REGISTER 0x3f + /** * struct smb347_charger - smb347 charger instance * @lock: protects concurrent access to online variables - * @client: pointer to i2c client + * @dev: pointer to device + * @regmap: pointer to driver regmap * @mains: power_supply instance for AC/DC power * @usb: power_supply instance for USB power * @battery: power_supply instance for battery * @mains_online: is AC/DC input connected * @usb_online: is USB input connected * @charging_enabled: is charging enabled - * @dentry: for debugfs * @pdata: pointer to platform data */ struct smb347_charger { struct mutex lock; - struct i2c_client *client; + struct device *dev; + struct regmap *regmap; struct power_supply mains; struct power_supply usb; struct power_supply battery; bool mains_online; bool usb_online; bool charging_enabled; - struct dentry *dentry; const struct smb347_charger_platform_data *pdata; }; @@ -193,14 +196,6 @@ static const unsigned int ccc_tbl[] = { 1200000, }; -/* Convert register value to current using lookup table */ -static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val) -{ - if (val >= size) - return -EINVAL; - return tbl[val]; -} - /* Convert current to register value using lookup table */ static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) { @@ -212,43 +207,22 @@ static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) return i > 0 ? i - 1 : -EINVAL; } -static int smb347_read(struct smb347_charger *smb, u8 reg) -{ - int ret; - - ret = i2c_smbus_read_byte_data(smb->client, reg); - if (ret < 0) - dev_warn(&smb->client->dev, "failed to read reg 0x%x: %d\n", - reg, ret); - return ret; -} - -static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val) -{ - int ret; - - ret = i2c_smbus_write_byte_data(smb->client, reg, val); - if (ret < 0) - dev_warn(&smb->client->dev, "failed to write reg 0x%x: %d\n", - reg, ret); - return ret; -} - /** - * smb347_update_status - updates the charging status + * smb347_update_ps_status - refreshes the power source status * @smb: pointer to smb347 charger instance * - * Function checks status of the charging and updates internal state - * accordingly. Returns %0 if there is no change in status, %1 if the - * status has changed and negative errno in case of failure. + * Function checks whether any power source is connected to the charger and + * updates internal state accordingly. If there is a change to previous state + * function returns %1, otherwise %0 and negative errno in case of errror. */ -static int smb347_update_status(struct smb347_charger *smb) +static int smb347_update_ps_status(struct smb347_charger *smb) { bool usb = false; bool dc = false; + unsigned int val; int ret; - ret = smb347_read(smb, IRQSTAT_E); + ret = regmap_read(smb->regmap, IRQSTAT_E, &val); if (ret < 0) return ret; @@ -257,9 +231,9 @@ static int smb347_update_status(struct smb347_charger *smb) * platform data _and_ whether corresponding undervoltage is set. */ if (smb->pdata->use_mains) - dc = !(ret & IRQSTAT_E_DCIN_UV_STAT); + dc = !(val & IRQSTAT_E_DCIN_UV_STAT); if (smb->pdata->use_usb) - usb = !(ret & IRQSTAT_E_USBIN_UV_STAT); + usb = !(val & IRQSTAT_E_USBIN_UV_STAT); mutex_lock(&smb->lock); ret = smb->mains_online != dc || smb->usb_online != usb; @@ -271,15 +245,15 @@ static int smb347_update_status(struct smb347_charger *smb) } /* - * smb347_is_online - returns whether input power source is connected + * smb347_is_ps_online - returns whether input power source is connected * @smb: pointer to smb347 charger instance * * Returns %true if input power source is connected. Note that this is * dependent on what platform has configured for usable power sources. For - * example if USB is disabled, this will return %false even if the USB - * cable is connected. + * example if USB is disabled, this will return %false even if the USB cable + * is connected. */ -static bool smb347_is_online(struct smb347_charger *smb) +static bool smb347_is_ps_online(struct smb347_charger *smb) { bool ret; @@ -299,16 +273,17 @@ static bool smb347_is_online(struct smb347_charger *smb) */ static int smb347_charging_status(struct smb347_charger *smb) { + unsigned int val; int ret; - if (!smb347_is_online(smb)) + if (!smb347_is_ps_online(smb)) return 0; - ret = smb347_read(smb, STAT_C); + ret = regmap_read(smb->regmap, STAT_C, &val); if (ret < 0) return 0; - return (ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT; + return (val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT; } static int smb347_charging_set(struct smb347_charger *smb, bool enable) @@ -316,27 +291,17 @@ static int smb347_charging_set(struct smb347_charger *smb, bool enable) int ret = 0; if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { - dev_dbg(&smb->client->dev, - "charging enable/disable in SW disabled\n"); + dev_dbg(smb->dev, "charging enable/disable in SW disabled\n"); return 0; } mutex_lock(&smb->lock); if (smb->charging_enabled != enable) { - ret = smb347_read(smb, CMD_A); - if (ret < 0) - goto out; - - smb->charging_enabled = enable; - - if (enable) - ret |= CMD_A_CHG_ENABLED; - else - ret &= ~CMD_A_CHG_ENABLED; - - ret = smb347_write(smb, CMD_A, ret); + ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED, + enable ? CMD_A_CHG_ENABLED : 0); + if (!ret) + smb->charging_enabled = enable; } -out: mutex_unlock(&smb->lock); return ret; } @@ -351,7 +316,7 @@ static inline int smb347_charging_disable(struct smb347_charger *smb) return smb347_charging_set(smb, false); } -static int smb347_update_online(struct smb347_charger *smb) +static int smb347_start_stop_charging(struct smb347_charger *smb) { int ret; @@ -360,16 +325,14 @@ static int smb347_update_online(struct smb347_charger *smb) * disable or enable the charging. We do it manually because it * depends on how the platform has configured the valid inputs. */ - if (smb347_is_online(smb)) { + if (smb347_is_ps_online(smb)) { ret = smb347_charging_enable(smb); if (ret < 0) - dev_err(&smb->client->dev, - "failed to enable charging\n"); + dev_err(smb->dev, "failed to enable charging\n"); } else { ret = smb347_charging_disable(smb); if (ret < 0) - dev_err(&smb->client->dev, - "failed to disable charging\n"); + dev_err(smb->dev, "failed to disable charging\n"); } return ret; @@ -377,112 +340,120 @@ static int smb347_update_online(struct smb347_charger *smb) static int smb347_set_charge_current(struct smb347_charger *smb) { - int ret, val; - - ret = smb347_read(smb, CFG_CHARGE_CURRENT); - if (ret < 0) - return ret; + int ret; if (smb->pdata->max_charge_current) { - val = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), + ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), smb->pdata->max_charge_current); - if (val < 0) - return val; + if (ret < 0) + return ret; - ret &= ~CFG_CHARGE_CURRENT_FCC_MASK; - ret |= val << CFG_CHARGE_CURRENT_FCC_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_FCC_MASK, + ret << CFG_CHARGE_CURRENT_FCC_SHIFT); + if (ret < 0) + return ret; } if (smb->pdata->pre_charge_current) { - val = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), + ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), smb->pdata->pre_charge_current); - if (val < 0) - return val; + if (ret < 0) + return ret; - ret &= ~CFG_CHARGE_CURRENT_PCC_MASK; - ret |= val << CFG_CHARGE_CURRENT_PCC_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_PCC_MASK, + ret << CFG_CHARGE_CURRENT_PCC_SHIFT); + if (ret < 0) + return ret; } if (smb->pdata->termination_current) { - val = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), + ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), smb->pdata->termination_current); - if (val < 0) - return val; + if (ret < 0) + return ret; - ret &= ~CFG_CHARGE_CURRENT_TC_MASK; - ret |= val; + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_TC_MASK, ret); + if (ret < 0) + return ret; } - return smb347_write(smb, CFG_CHARGE_CURRENT, ret); + return 0; } static int smb347_set_current_limits(struct smb347_charger *smb) { - int ret, val; - - ret = smb347_read(smb, CFG_CURRENT_LIMIT); - if (ret < 0) - return ret; + int ret; if (smb->pdata->mains_current_limit) { - val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), smb->pdata->mains_current_limit); - if (val < 0) - return val; + if (ret < 0) + return ret; - ret &= ~CFG_CURRENT_LIMIT_DC_MASK; - ret |= val << CFG_CURRENT_LIMIT_DC_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, + CFG_CURRENT_LIMIT_DC_MASK, + ret << CFG_CURRENT_LIMIT_DC_SHIFT); + if (ret < 0) + return ret; } if (smb->pdata->usb_hc_current_limit) { - val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), smb->pdata->usb_hc_current_limit); - if (val < 0) - return val; + if (ret < 0) + return ret; - ret &= ~CFG_CURRENT_LIMIT_USB_MASK; - ret |= val; + ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, + CFG_CURRENT_LIMIT_USB_MASK, ret); + if (ret < 0) + return ret; } - return smb347_write(smb, CFG_CURRENT_LIMIT, ret); + return 0; } static int smb347_set_voltage_limits(struct smb347_charger *smb) { - int ret, val; - - ret = smb347_read(smb, CFG_FLOAT_VOLTAGE); - if (ret < 0) - return ret; + int ret; if (smb->pdata->pre_to_fast_voltage) { - val = smb->pdata->pre_to_fast_voltage; + ret = smb->pdata->pre_to_fast_voltage; /* uV */ - val = clamp_val(val, 2400000, 3000000) - 2400000; - val /= 200000; + ret = clamp_val(ret, 2400000, 3000000) - 2400000; + ret /= 200000; - ret &= ~CFG_FLOAT_VOLTAGE_THRESHOLD_MASK; - ret |= val << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, + CFG_FLOAT_VOLTAGE_THRESHOLD_MASK, + ret << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT); + if (ret < 0) + return ret; } if (smb->pdata->max_charge_voltage) { - val = smb->pdata->max_charge_voltage; + ret = smb->pdata->max_charge_voltage; /* uV */ - val = clamp_val(val, 3500000, 4500000) - 3500000; - val /= 20000; + ret = clamp_val(ret, 3500000, 4500000) - 3500000; + ret /= 20000; - ret |= val; + ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, + CFG_FLOAT_VOLTAGE_FLOAT_MASK, ret); + if (ret < 0) + return ret; } - return smb347_write(smb, CFG_FLOAT_VOLTAGE, ret); + return 0; } static int smb347_set_temp_limits(struct smb347_charger *smb) { bool enable_therm_monitor = false; - int ret, val; + int ret = 0; + int val; if (smb->pdata->chip_temp_threshold) { val = smb->pdata->chip_temp_threshold; @@ -491,22 +462,13 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) val = clamp_val(val, 100, 130) - 100; val /= 10; - ret = smb347_read(smb, CFG_OTG); - if (ret < 0) - return ret; - - ret &= ~CFG_OTG_TEMP_THRESHOLD_MASK; - ret |= val << CFG_OTG_TEMP_THRESHOLD_SHIFT; - - ret = smb347_write(smb, CFG_OTG, ret); + ret = regmap_update_bits(smb->regmap, CFG_OTG, + CFG_OTG_TEMP_THRESHOLD_MASK, + val << CFG_OTG_TEMP_THRESHOLD_SHIFT); if (ret < 0) return ret; } - ret = smb347_read(smb, CFG_TEMP_LIMIT); - if (ret < 0) - return ret; - if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { val = smb->pdata->soft_cold_temp_limit; @@ -515,8 +477,11 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) /* this goes from higher to lower so invert the value */ val = ~val & 0x3; - ret &= ~CFG_TEMP_LIMIT_SOFT_COLD_MASK; - ret |= val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_SOFT_COLD_MASK, + val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT); + if (ret < 0) + return ret; enable_therm_monitor = true; } @@ -527,8 +492,11 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) val = clamp_val(val, 40, 55) - 40; val /= 5; - ret &= ~CFG_TEMP_LIMIT_SOFT_HOT_MASK; - ret |= val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_SOFT_HOT_MASK, + val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT); + if (ret < 0) + return ret; enable_therm_monitor = true; } @@ -541,8 +509,11 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) /* this goes from higher to lower so invert the value */ val = ~val & 0x3; - ret &= ~CFG_TEMP_LIMIT_HARD_COLD_MASK; - ret |= val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_HARD_COLD_MASK, + val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT); + if (ret < 0) + return ret; enable_therm_monitor = true; } @@ -553,16 +524,15 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) val = clamp_val(val, 50, 65) - 50; val /= 5; - ret &= ~CFG_TEMP_LIMIT_HARD_HOT_MASK; - ret |= val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT; + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_HARD_HOT_MASK, + val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT); + if (ret < 0) + return ret; enable_therm_monitor = true; } - ret = smb347_write(smb, CFG_TEMP_LIMIT, ret); - if (ret < 0) - return ret; - /* * If any of the temperature limits are set, we also enable the * thermistor monitoring. @@ -574,25 +544,15 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) * depending on the configuration. */ if (enable_therm_monitor) { - ret = smb347_read(smb, CFG_THERM); - if (ret < 0) - return ret; - - ret &= ~CFG_THERM_MONITOR_DISABLED; - - ret = smb347_write(smb, CFG_THERM, ret); + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_MONITOR_DISABLED, 0); if (ret < 0) return ret; } if (smb->pdata->suspend_on_hard_temp_limit) { - ret = smb347_read(smb, CFG_SYSOK); - if (ret < 0) - return ret; - - ret &= ~CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED; - - ret = smb347_write(smb, CFG_SYSOK, ret); + ret = regmap_update_bits(smb->regmap, CFG_SYSOK, + CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0); if (ret < 0) return ret; } @@ -601,17 +561,15 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) { val = smb->pdata->soft_temp_limit_compensation & 0x3; - ret = smb347_read(smb, CFG_THERM); + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_SOFT_HOT_COMPENSATION_MASK, + val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT); if (ret < 0) return ret; - ret &= ~CFG_THERM_SOFT_HOT_COMPENSATION_MASK; - ret |= val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT; - - ret &= ~CFG_THERM_SOFT_COLD_COMPENSATION_MASK; - ret |= val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT; - - ret = smb347_write(smb, CFG_THERM, ret); + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_SOFT_COLD_COMPENSATION_MASK, + val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT); if (ret < 0) return ret; } @@ -622,14 +580,9 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) if (val < 0) return val; - ret = smb347_read(smb, CFG_OTG); - if (ret < 0) - return ret; - - ret &= ~CFG_OTG_CC_COMPENSATION_MASK; - ret |= (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT; - - ret = smb347_write(smb, CFG_OTG, ret); + ret = regmap_update_bits(smb->regmap, CFG_OTG, + CFG_OTG_CC_COMPENSATION_MASK, + (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT); if (ret < 0) return ret; } @@ -648,22 +601,13 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) */ static int smb347_set_writable(struct smb347_charger *smb, bool writable) { - int ret; - - ret = smb347_read(smb, CMD_A); - if (ret < 0) - return ret; - - if (writable) - ret |= CMD_A_ALLOW_WRITE; - else - ret &= ~CMD_A_ALLOW_WRITE; - - return smb347_write(smb, CMD_A, ret); + return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE, + writable ? CMD_A_ALLOW_WRITE : 0); } static int smb347_hw_init(struct smb347_charger *smb) { + unsigned int val; int ret; ret = smb347_set_writable(smb, true); @@ -692,34 +636,19 @@ static int smb347_hw_init(struct smb347_charger *smb) /* If USB charging is disabled we put the USB in suspend mode */ if (!smb->pdata->use_usb) { - ret = smb347_read(smb, CMD_A); - if (ret < 0) - goto fail; - - ret |= CMD_A_SUSPEND_ENABLED; - - ret = smb347_write(smb, CMD_A, ret); + ret = regmap_update_bits(smb->regmap, CMD_A, + CMD_A_SUSPEND_ENABLED, + CMD_A_SUSPEND_ENABLED); if (ret < 0) goto fail; } - ret = smb347_read(smb, CFG_OTHER); - if (ret < 0) - goto fail; - /* * If configured by platform data, we enable hardware Auto-OTG * support for driving VBUS. Otherwise we disable it. */ - ret &= ~CFG_OTHER_RID_MASK; - if (smb->pdata->use_usb_otg) - ret |= CFG_OTHER_RID_ENABLED_AUTO_OTG; - - ret = smb347_write(smb, CFG_OTHER, ret); - if (ret < 0) - goto fail; - - ret = smb347_read(smb, CFG_PIN); + ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK, + smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0); if (ret < 0) goto fail; @@ -728,32 +657,33 @@ static int smb347_hw_init(struct smb347_charger *smb) * command register unless pin control is specified in the platform * data. */ - ret &= ~CFG_PIN_EN_CTRL_MASK; - switch (smb->pdata->enable_control) { - case SMB347_CHG_ENABLE_SW: - /* Do nothing, 0 means i2c control */ - break; case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW: - ret |= CFG_PIN_EN_CTRL_ACTIVE_LOW; + val = CFG_PIN_EN_CTRL_ACTIVE_LOW; break; case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH: - ret |= CFG_PIN_EN_CTRL_ACTIVE_HIGH; + val = CFG_PIN_EN_CTRL_ACTIVE_HIGH; + break; + default: + val = 0; break; } - /* Disable Automatic Power Source Detection (APSD) interrupt. */ - ret &= ~CFG_PIN_EN_APSD_IRQ; + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL_MASK, + val); + if (ret < 0) + goto fail; - ret = smb347_write(smb, CFG_PIN, ret); + /* Disable Automatic Power Source Detection (APSD) interrupt. */ + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_APSD_IRQ, 0); if (ret < 0) goto fail; - ret = smb347_update_status(smb); + ret = smb347_update_ps_status(smb); if (ret < 0) goto fail; - ret = smb347_update_online(smb); + ret = smb347_start_stop_charging(smb); fail: smb347_set_writable(smb, false); @@ -763,24 +693,25 @@ fail: static irqreturn_t smb347_interrupt(int irq, void *data) { struct smb347_charger *smb = data; - int stat_c, irqstat_e, irqstat_c; - irqreturn_t ret = IRQ_NONE; + unsigned int stat_c, irqstat_e, irqstat_c; + bool handled = false; + int ret; - stat_c = smb347_read(smb, STAT_C); - if (stat_c < 0) { - dev_warn(&smb->client->dev, "reading STAT_C failed\n"); + ret = regmap_read(smb->regmap, STAT_C, &stat_c); + if (ret < 0) { + dev_warn(smb->dev, "reading STAT_C failed\n"); return IRQ_NONE; } - irqstat_c = smb347_read(smb, IRQSTAT_C); - if (irqstat_c < 0) { - dev_warn(&smb->client->dev, "reading IRQSTAT_C failed\n"); + ret = regmap_read(smb->regmap, IRQSTAT_C, &irqstat_c); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_C failed\n"); return IRQ_NONE; } - irqstat_e = smb347_read(smb, IRQSTAT_E); - if (irqstat_e < 0) { - dev_warn(&smb->client->dev, "reading IRQSTAT_E failed\n"); + ret = regmap_read(smb->regmap, IRQSTAT_E, &irqstat_e); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_E failed\n"); return IRQ_NONE; } @@ -789,13 +720,11 @@ static irqreturn_t smb347_interrupt(int irq, void *data) * disable charging. */ if (stat_c & STAT_C_CHARGER_ERROR) { - dev_err(&smb->client->dev, - "error in charger, disabling charging\n"); + dev_err(smb->dev, "error in charger, disabling charging\n"); smb347_charging_disable(smb); power_supply_changed(&smb->battery); - - ret = IRQ_HANDLED; + handled = true; } /* @@ -806,7 +735,7 @@ static irqreturn_t smb347_interrupt(int irq, void *data) if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) { if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) power_supply_changed(&smb->battery); - ret = IRQ_HANDLED; + handled = true; } /* @@ -814,15 +743,17 @@ static irqreturn_t smb347_interrupt(int irq, void *data) * was connected or disconnected. */ if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) { - if (smb347_update_status(smb) > 0) { - smb347_update_online(smb); - power_supply_changed(&smb->mains); - power_supply_changed(&smb->usb); + if (smb347_update_ps_status(smb) > 0) { + smb347_start_stop_charging(smb); + if (smb->pdata->use_mains) + power_supply_changed(&smb->mains); + if (smb->pdata->use_usb) + power_supply_changed(&smb->usb); } - ret = IRQ_HANDLED; + handled = true; } - return ret; + return handled ? IRQ_HANDLED : IRQ_NONE; } static int smb347_irq_set(struct smb347_charger *smb, bool enable) @@ -839,41 +770,18 @@ static int smb347_irq_set(struct smb347_charger *smb, bool enable) * - termination current reached * - charger error */ - if (enable) { - ret = smb347_write(smb, CFG_FAULT_IRQ, CFG_FAULT_IRQ_DCIN_UV); - if (ret < 0) - goto fail; - - ret = smb347_write(smb, CFG_STATUS_IRQ, - CFG_STATUS_IRQ_TERMINATION_OR_TAPER); - if (ret < 0) - goto fail; - - ret = smb347_read(smb, CFG_PIN); - if (ret < 0) - goto fail; - - ret |= CFG_PIN_EN_CHARGER_ERROR; - - ret = smb347_write(smb, CFG_PIN, ret); - } else { - ret = smb347_write(smb, CFG_FAULT_IRQ, 0); - if (ret < 0) - goto fail; - - ret = smb347_write(smb, CFG_STATUS_IRQ, 0); - if (ret < 0) - goto fail; - - ret = smb347_read(smb, CFG_PIN); - if (ret < 0) - goto fail; - - ret &= ~CFG_PIN_EN_CHARGER_ERROR; + ret = regmap_update_bits(smb->regmap, CFG_FAULT_IRQ, 0xff, + enable ? CFG_FAULT_IRQ_DCIN_UV : 0); + if (ret < 0) + goto fail; - ret = smb347_write(smb, CFG_PIN, ret); - } + ret = regmap_update_bits(smb->regmap, CFG_STATUS_IRQ, 0xff, + enable ? CFG_STATUS_IRQ_TERMINATION_OR_TAPER : 0); + if (ret < 0) + goto fail; + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR, + enable ? CFG_PIN_EN_CHARGER_ERROR : 0); fail: smb347_set_writable(smb, false); return ret; @@ -889,18 +797,18 @@ static inline int smb347_irq_disable(struct smb347_charger *smb) return smb347_irq_set(smb, false); } -static int smb347_irq_init(struct smb347_charger *smb) +static int smb347_irq_init(struct smb347_charger *smb, + struct i2c_client *client) { const struct smb347_charger_platform_data *pdata = smb->pdata; int ret, irq = gpio_to_irq(pdata->irq_gpio); - ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, smb->client->name); + ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); if (ret < 0) goto fail; ret = request_threaded_irq(irq, NULL, smb347_interrupt, - IRQF_TRIGGER_FALLING, smb->client->name, - smb); + IRQF_TRIGGER_FALLING, client->name, smb); if (ret < 0) goto fail_gpio; @@ -912,23 +820,14 @@ static int smb347_irq_init(struct smb347_charger *smb) * Configure the STAT output to be suitable for interrupts: disable * all other output (except interrupts) and make it active low. */ - ret = smb347_read(smb, CFG_STAT); - if (ret < 0) - goto fail_readonly; - - ret &= ~CFG_STAT_ACTIVE_HIGH; - ret |= CFG_STAT_DISABLED; - - ret = smb347_write(smb, CFG_STAT, ret); - if (ret < 0) - goto fail_readonly; - - ret = smb347_irq_enable(smb); + ret = regmap_update_bits(smb->regmap, CFG_STAT, + CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED, + CFG_STAT_DISABLED); if (ret < 0) goto fail_readonly; smb347_set_writable(smb, false); - smb->client->irq = irq; + client->irq = irq; return 0; fail_readonly: @@ -938,7 +837,7 @@ fail_irq: fail_gpio: gpio_free(pdata->irq_gpio); fail: - smb->client->irq = 0; + client->irq = 0; return ret; } @@ -987,13 +886,13 @@ static int smb347_battery_get_property(struct power_supply *psy, const struct smb347_charger_platform_data *pdata = smb->pdata; int ret; - ret = smb347_update_status(smb); + ret = smb347_update_ps_status(smb); if (ret < 0) return ret; switch (prop) { case POWER_SUPPLY_PROP_STATUS: - if (!smb347_is_online(smb)) { + if (!smb347_is_ps_online(smb)) { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; } @@ -1004,7 +903,7 @@ static int smb347_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CHARGE_TYPE: - if (!smb347_is_online(smb)) + if (!smb347_is_ps_online(smb)) return -ENODATA; /* @@ -1036,44 +935,6 @@ static int smb347_battery_get_property(struct power_supply *psy, val->intval = pdata->battery_info.voltage_max_design; break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (!smb347_is_online(smb)) - return -ENODATA; - ret = smb347_read(smb, STAT_A); - if (ret < 0) - return ret; - - ret &= STAT_A_FLOAT_VOLTAGE_MASK; - if (ret > 0x3d) - ret = 0x3d; - - val->intval = 3500000 + ret * 20000; - break; - - case POWER_SUPPLY_PROP_CURRENT_NOW: - if (!smb347_is_online(smb)) - return -ENODATA; - - ret = smb347_read(smb, STAT_B); - if (ret < 0) - return ret; - - /* - * The current value is composition of FCC and PCC values - * and we can detect which table to use from bit 5. - */ - if (ret & 0x20) { - val->intval = hw_to_current(fcc_tbl, - ARRAY_SIZE(fcc_tbl), - ret & 7); - } else { - ret >>= 3; - val->intval = hw_to_current(pcc_tbl, - ARRAY_SIZE(pcc_tbl), - ret & 7); - } - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: val->intval = pdata->battery_info.charge_full_design; break; @@ -1095,64 +956,58 @@ static enum power_supply_property smb347_battery_properties[] = { POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_MODEL_NAME, }; -static int smb347_debugfs_show(struct seq_file *s, void *data) +static bool smb347_volatile_reg(struct device *dev, unsigned int reg) { - struct smb347_charger *smb = s->private; - int ret; - u8 reg; - - seq_printf(s, "Control registers:\n"); - seq_printf(s, "==================\n"); - for (reg = CFG_CHARGE_CURRENT; reg <= CFG_ADDRESS; reg++) { - ret = smb347_read(smb, reg); - seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret); - } - seq_printf(s, "\n"); - - seq_printf(s, "Command registers:\n"); - seq_printf(s, "==================\n"); - ret = smb347_read(smb, CMD_A); - seq_printf(s, "0x%02x:\t0x%02x\n", CMD_A, ret); - ret = smb347_read(smb, CMD_B); - seq_printf(s, "0x%02x:\t0x%02x\n", CMD_B, ret); - ret = smb347_read(smb, CMD_C); - seq_printf(s, "0x%02x:\t0x%02x\n", CMD_C, ret); - seq_printf(s, "\n"); - - seq_printf(s, "Interrupt status registers:\n"); - seq_printf(s, "===========================\n"); - for (reg = IRQSTAT_A; reg <= IRQSTAT_F; reg++) { - ret = smb347_read(smb, reg); - seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret); - } - seq_printf(s, "\n"); - - seq_printf(s, "Status registers:\n"); - seq_printf(s, "=================\n"); - for (reg = STAT_A; reg <= STAT_E; reg++) { - ret = smb347_read(smb, reg); - seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret); + switch (reg) { + case IRQSTAT_A: + case IRQSTAT_C: + case IRQSTAT_E: + case IRQSTAT_F: + case STAT_A: + case STAT_B: + case STAT_C: + case STAT_E: + return true; } - return 0; + return false; } -static int smb347_debugfs_open(struct inode *inode, struct file *file) +static bool smb347_readable_reg(struct device *dev, unsigned int reg) { - return single_open(file, smb347_debugfs_show, inode->i_private); + switch (reg) { + case CFG_CHARGE_CURRENT: + case CFG_CURRENT_LIMIT: + case CFG_FLOAT_VOLTAGE: + case CFG_STAT: + case CFG_PIN: + case CFG_THERM: + case CFG_SYSOK: + case CFG_OTHER: + case CFG_OTG: + case CFG_TEMP_LIMIT: + case CFG_FAULT_IRQ: + case CFG_STATUS_IRQ: + case CFG_ADDRESS: + case CMD_A: + case CMD_B: + case CMD_C: + return true; + } + + return smb347_volatile_reg(dev, reg); } -static const struct file_operations smb347_debugfs_fops = { - .open = smb347_debugfs_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, +static const struct regmap_config smb347_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SMB347_MAX_REGISTER, + .volatile_reg = smb347_volatile_reg, + .readable_reg = smb347_readable_reg, }; static int smb347_probe(struct i2c_client *client, @@ -1178,28 +1033,45 @@ static int smb347_probe(struct i2c_client *client, i2c_set_clientdata(client, smb); mutex_init(&smb->lock); - smb->client = client; + smb->dev = &client->dev; smb->pdata = pdata; + smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap); + if (IS_ERR(smb->regmap)) + return PTR_ERR(smb->regmap); + ret = smb347_hw_init(smb); if (ret < 0) return ret; - smb->mains.name = "smb347-mains"; - smb->mains.type = POWER_SUPPLY_TYPE_MAINS; - smb->mains.get_property = smb347_mains_get_property; - smb->mains.properties = smb347_mains_properties; - smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties); - smb->mains.supplied_to = battery; - smb->mains.num_supplicants = ARRAY_SIZE(battery); - - smb->usb.name = "smb347-usb"; - smb->usb.type = POWER_SUPPLY_TYPE_USB; - smb->usb.get_property = smb347_usb_get_property; - smb->usb.properties = smb347_usb_properties; - smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties); - smb->usb.supplied_to = battery; - smb->usb.num_supplicants = ARRAY_SIZE(battery); + if (smb->pdata->use_mains) { + smb->mains.name = "smb347-mains"; + smb->mains.type = POWER_SUPPLY_TYPE_MAINS; + smb->mains.get_property = smb347_mains_get_property; + smb->mains.properties = smb347_mains_properties; + smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties); + smb->mains.supplied_to = battery; + smb->mains.num_supplicants = ARRAY_SIZE(battery); + ret = power_supply_register(dev, &smb->mains); + if (ret < 0) + return ret; + } + + if (smb->pdata->use_usb) { + smb->usb.name = "smb347-usb"; + smb->usb.type = POWER_SUPPLY_TYPE_USB; + smb->usb.get_property = smb347_usb_get_property; + smb->usb.properties = smb347_usb_properties; + smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties); + smb->usb.supplied_to = battery; + smb->usb.num_supplicants = ARRAY_SIZE(battery); + ret = power_supply_register(dev, &smb->usb); + if (ret < 0) { + if (smb->pdata->use_mains) + power_supply_unregister(&smb->mains); + return ret; + } + } smb->battery.name = "smb347-battery"; smb->battery.type = POWER_SUPPLY_TYPE_BATTERY; @@ -1207,20 +1079,13 @@ static int smb347_probe(struct i2c_client *client, smb->battery.properties = smb347_battery_properties; smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties); - ret = power_supply_register(dev, &smb->mains); - if (ret < 0) - return ret; - - ret = power_supply_register(dev, &smb->usb); - if (ret < 0) { - power_supply_unregister(&smb->mains); - return ret; - } ret = power_supply_register(dev, &smb->battery); if (ret < 0) { - power_supply_unregister(&smb->usb); - power_supply_unregister(&smb->mains); + if (smb->pdata->use_usb) + power_supply_unregister(&smb->usb); + if (smb->pdata->use_mains) + power_supply_unregister(&smb->mains); return ret; } @@ -1229,15 +1094,15 @@ static int smb347_probe(struct i2c_client *client, * interrupt support here. */ if (pdata->irq_gpio >= 0) { - ret = smb347_irq_init(smb); + ret = smb347_irq_init(smb, client); if (ret < 0) { dev_warn(dev, "failed to initialize IRQ: %d\n", ret); dev_warn(dev, "disabling IRQ support\n"); + } else { + smb347_irq_enable(smb); } } - smb->dentry = debugfs_create_file("smb347-regs", S_IRUSR, NULL, smb, - &smb347_debugfs_fops); return 0; } @@ -1245,9 +1110,6 @@ static int smb347_remove(struct i2c_client *client) { struct smb347_charger *smb = i2c_get_clientdata(client); - if (!IS_ERR_OR_NULL(smb->dentry)) - debugfs_remove(smb->dentry); - if (client->irq) { smb347_irq_disable(smb); free_irq(client->irq, smb); @@ -1255,8 +1117,10 @@ static int smb347_remove(struct i2c_client *client) } power_supply_unregister(&smb->battery); - power_supply_unregister(&smb->usb); - power_supply_unregister(&smb->mains); + if (smb->pdata->use_usb) + power_supply_unregister(&smb->usb); + if (smb->pdata->use_mains) + power_supply_unregister(&smb->mains); return 0; } @@ -1275,17 +1139,7 @@ static struct i2c_driver smb347_driver = { .id_table = smb347_id, }; -static int __init smb347_init(void) -{ - return i2c_add_driver(&smb347_driver); -} -module_init(smb347_init); - -static void __exit smb347_exit(void) -{ - i2c_del_driver(&smb347_driver); -} -module_exit(smb347_exit); +module_i2c_driver(smb347_driver); MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>"); MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index d6f8adaa26e..8ea7bccc710 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -78,7 +78,7 @@ typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int avail); * the recovery of the remote processor. */ static int rproc_iommu_fault(struct iommu_domain *domain, struct device *dev, - unsigned long iova, int flags) + unsigned long iova, int flags, void *token) { dev_err(dev, "iommu fault: da 0x%lx flags 0x%x\n", iova, flags); @@ -117,7 +117,7 @@ static int rproc_enable_iommu(struct rproc *rproc) return -ENOMEM; } - iommu_set_fault_handler(domain, rproc_iommu_fault); + iommu_set_fault_handler(domain, rproc_iommu_fault, rproc); ret = iommu_attach_device(domain, dev); if (ret) { diff --git a/drivers/s390/block/dasd_int.h b/drivers/s390/block/dasd_int.h index 33a6743ddc5..c05da00583f 100644 --- a/drivers/s390/block/dasd_int.h +++ b/drivers/s390/block/dasd_int.h @@ -10,8 +10,6 @@ #ifndef DASD_INT_H #define DASD_INT_H -#ifdef __KERNEL__ - /* we keep old device allocation scheme; IOW, minors are still in 0..255 */ #define DASD_PER_MAJOR (1U << (MINORBITS - DASD_PARTN_BITS)) #define DASD_PARTN_MASK ((1 << DASD_PARTN_BITS) - 1) @@ -791,6 +789,4 @@ static inline int dasd_eer_enabled(struct dasd_device *device) #define dasd_eer_enabled(d) (0) #endif /* CONFIG_DASD_ERR */ -#endif /* __KERNEL__ */ - #endif /* DASD_H */ diff --git a/drivers/s390/char/sclp_sdias.c b/drivers/s390/char/sclp_sdias.c index 69e6c50d4cf..50f7115990f 100644 --- a/drivers/s390/char/sclp_sdias.c +++ b/drivers/s390/char/sclp_sdias.c @@ -211,7 +211,7 @@ int sclp_sdias_copy(void *dest, int start_blk, int nr_blks) sccb.evbuf.event_qual = EQ_STORE_DATA; sccb.evbuf.data_id = DI_FCP_DUMP; sccb.evbuf.event_id = 4712; -#ifdef __s390x__ +#ifdef CONFIG_64BIT sccb.evbuf.asa_size = ASA_SIZE_64; #else sccb.evbuf.asa_size = ASA_SIZE_32; diff --git a/drivers/scsi/be2iscsi/be_mgmt.c b/drivers/scsi/be2iscsi/be_mgmt.c index 01bb04cd9e7..2a096795b9a 100644 --- a/drivers/scsi/be2iscsi/be_mgmt.c +++ b/drivers/scsi/be2iscsi/be_mgmt.c @@ -571,13 +571,12 @@ free_cmd: static int mgmt_alloc_cmd_data(struct beiscsi_hba *phba, struct be_dma_mem *cmd, int iscsi_cmd, int size) { - cmd->va = pci_alloc_consistent(phba->ctrl.pdev, sizeof(size), - &cmd->dma); + cmd->va = pci_alloc_consistent(phba->ctrl.pdev, size, &cmd->dma); if (!cmd->va) { SE_DEBUG(DBG_LVL_1, "Failed to allocate memory for if info\n"); return -ENOMEM; } - memset(cmd->va, 0, sizeof(size)); + memset(cmd->va, 0, size); cmd->size = size; be_cmd_hdr_prepare(cmd->va, CMD_SUBSYSTEM_ISCSI, iscsi_cmd, size); return 0; diff --git a/drivers/scsi/bfa/bfad_attr.c b/drivers/scsi/bfa/bfad_attr.c index 8b6c6bf7837..b8392744017 100644 --- a/drivers/scsi/bfa/bfad_attr.c +++ b/drivers/scsi/bfa/bfad_attr.c @@ -426,6 +426,23 @@ bfad_im_vport_create(struct fc_vport *fc_vport, bool disable) vshost = vport->drv_port.im_port->shost; fc_host_node_name(vshost) = wwn_to_u64((u8 *)&port_cfg.nwwn); fc_host_port_name(vshost) = wwn_to_u64((u8 *)&port_cfg.pwwn); + fc_host_supported_classes(vshost) = FC_COS_CLASS3; + + memset(fc_host_supported_fc4s(vshost), 0, + sizeof(fc_host_supported_fc4s(vshost))); + + /* For FCP type 0x08 */ + if (supported_fc4s & BFA_LPORT_ROLE_FCP_IM) + fc_host_supported_fc4s(vshost)[2] = 1; + + /* For fibre channel services type 0x20 */ + fc_host_supported_fc4s(vshost)[7] = 1; + + fc_host_supported_speeds(vshost) = + bfad_im_supported_speeds(&bfad->bfa); + fc_host_maxframe_size(vshost) = + bfa_fcport_get_maxfrsize(&bfad->bfa); + fc_vport->dd_data = vport; vport->drv_port.im_port->fc_vport = fc_vport; } else if (rc == BFA_STATUS_INVALID_WWN) diff --git a/drivers/scsi/bfa/bfad_im.c b/drivers/scsi/bfa/bfad_im.c index 3153923f5b6..1ac09afe35e 100644 --- a/drivers/scsi/bfa/bfad_im.c +++ b/drivers/scsi/bfa/bfad_im.c @@ -987,7 +987,7 @@ done: return 0; } -static u32 +u32 bfad_im_supported_speeds(struct bfa_s *bfa) { struct bfa_ioc_attr_s *ioc_attr; diff --git a/drivers/scsi/bfa/bfad_im.h b/drivers/scsi/bfa/bfad_im.h index 0814367ef10..f6c1023e502 100644 --- a/drivers/scsi/bfa/bfad_im.h +++ b/drivers/scsi/bfa/bfad_im.h @@ -37,6 +37,7 @@ int bfad_im_scsi_host_alloc(struct bfad_s *bfad, struct bfad_im_port_s *im_port, struct device *dev); void bfad_im_scsi_host_free(struct bfad_s *bfad, struct bfad_im_port_s *im_port); +u32 bfad_im_supported_speeds(struct bfa_s *bfa); #define MAX_FCP_TARGET 1024 #define MAX_FCP_LUN 16384 diff --git a/drivers/scsi/bnx2fc/bnx2fc.h b/drivers/scsi/bnx2fc/bnx2fc.h index a4953ef9e53..0578fa0dc14 100644 --- a/drivers/scsi/bnx2fc/bnx2fc.h +++ b/drivers/scsi/bnx2fc/bnx2fc.h @@ -62,7 +62,7 @@ #include "bnx2fc_constants.h" #define BNX2FC_NAME "bnx2fc" -#define BNX2FC_VERSION "1.0.10" +#define BNX2FC_VERSION "1.0.11" #define PFX "bnx2fc: " @@ -228,13 +228,16 @@ struct bnx2fc_interface { struct packet_type fip_packet_type; struct workqueue_struct *timer_work_queue; struct kref kref; - struct fcoe_ctlr ctlr; u8 vlan_enabled; int vlan_id; bool enabled; }; -#define bnx2fc_from_ctlr(fip) container_of(fip, struct bnx2fc_interface, ctlr) +#define bnx2fc_from_ctlr(x) \ + ((struct bnx2fc_interface *)((x) + 1)) + +#define bnx2fc_to_ctlr(x) \ + ((struct fcoe_ctlr *)(((struct fcoe_ctlr *)(x)) - 1)) struct bnx2fc_lport { struct list_head list; diff --git a/drivers/scsi/bnx2fc/bnx2fc_els.c b/drivers/scsi/bnx2fc/bnx2fc_els.c index ce0ce3e32f3..bdbbb13b853 100644 --- a/drivers/scsi/bnx2fc/bnx2fc_els.c +++ b/drivers/scsi/bnx2fc/bnx2fc_els.c @@ -854,7 +854,6 @@ static void bnx2fc_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, struct fc_exch *exch = fc_seq_exch(seq); struct fc_lport *lport = exch->lp; u8 *mac; - struct fc_frame_header *fh; u8 op; if (IS_ERR(fp)) @@ -862,13 +861,6 @@ static void bnx2fc_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, mac = fr_cb(fp)->granted_mac; if (is_zero_ether_addr(mac)) { - fh = fc_frame_header_get(fp); - if (fh->fh_type != FC_TYPE_ELS) { - printk(KERN_ERR PFX "bnx2fc_flogi_resp:" - "fh_type != FC_TYPE_ELS\n"); - fc_frame_free(fp); - return; - } op = fc_frame_payload_op(fp); if (lport->vport) { if (op == ELS_LS_RJT) { @@ -878,12 +870,10 @@ static void bnx2fc_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, return; } } - if (fcoe_ctlr_recv_flogi(fip, lport, fp)) { - fc_frame_free(fp); - return; - } + fcoe_ctlr_recv_flogi(fip, lport, fp); } - fip->update_mac(lport, mac); + if (!is_zero_ether_addr(mac)) + fip->update_mac(lport, mac); done: fc_lport_flogi_resp(seq, fp, lport); } @@ -910,7 +900,7 @@ struct fc_seq *bnx2fc_elsct_send(struct fc_lport *lport, u32 did, { struct fcoe_port *port = lport_priv(lport); struct bnx2fc_interface *interface = port->priv; - struct fcoe_ctlr *fip = &interface->ctlr; + struct fcoe_ctlr *fip = bnx2fc_to_ctlr(interface); struct fc_frame_header *fh = fc_frame_header_get(fp); switch (op) { diff --git a/drivers/scsi/bnx2fc/bnx2fc_fcoe.c b/drivers/scsi/bnx2fc/bnx2fc_fcoe.c index c1c6a92a0b9..f52f668fd24 100644 --- a/drivers/scsi/bnx2fc/bnx2fc_fcoe.c +++ b/drivers/scsi/bnx2fc/bnx2fc_fcoe.c @@ -22,7 +22,7 @@ DEFINE_PER_CPU(struct bnx2fc_percpu_s, bnx2fc_percpu); #define DRV_MODULE_NAME "bnx2fc" #define DRV_MODULE_VERSION BNX2FC_VERSION -#define DRV_MODULE_RELDATE "Jan 22, 2011" +#define DRV_MODULE_RELDATE "Apr 24, 2012" static char version[] __devinitdata = @@ -54,6 +54,7 @@ static struct cnic_ulp_ops bnx2fc_cnic_cb; static struct libfc_function_template bnx2fc_libfc_fcn_templ; static struct scsi_host_template bnx2fc_shost_template; static struct fc_function_template bnx2fc_transport_function; +static struct fcoe_sysfs_function_template bnx2fc_fcoe_sysfs_templ; static struct fc_function_template bnx2fc_vport_xport_function; static int bnx2fc_create(struct net_device *netdev, enum fip_state fip_mode); static void __bnx2fc_destroy(struct bnx2fc_interface *interface); @@ -88,6 +89,7 @@ static void bnx2fc_port_shutdown(struct fc_lport *lport); static void bnx2fc_stop(struct bnx2fc_interface *interface); static int __init bnx2fc_mod_init(void); static void __exit bnx2fc_mod_exit(void); +static void bnx2fc_ctlr_get_lesb(struct fcoe_ctlr_device *ctlr_dev); unsigned int bnx2fc_debug_level; module_param_named(debug_logging, bnx2fc_debug_level, int, S_IRUGO|S_IWUSR); @@ -118,6 +120,41 @@ static void bnx2fc_get_lesb(struct fc_lport *lport, __fcoe_get_lesb(lport, fc_lesb, netdev); } +static void bnx2fc_ctlr_get_lesb(struct fcoe_ctlr_device *ctlr_dev) +{ + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev); + struct net_device *netdev = bnx2fc_netdev(fip->lp); + struct fcoe_fc_els_lesb *fcoe_lesb; + struct fc_els_lesb fc_lesb; + + __fcoe_get_lesb(fip->lp, &fc_lesb, netdev); + fcoe_lesb = (struct fcoe_fc_els_lesb *)(&fc_lesb); + + ctlr_dev->lesb.lesb_link_fail = + ntohl(fcoe_lesb->lesb_link_fail); + ctlr_dev->lesb.lesb_vlink_fail = + ntohl(fcoe_lesb->lesb_vlink_fail); + ctlr_dev->lesb.lesb_miss_fka = + ntohl(fcoe_lesb->lesb_miss_fka); + ctlr_dev->lesb.lesb_symb_err = + ntohl(fcoe_lesb->lesb_symb_err); + ctlr_dev->lesb.lesb_err_block = + ntohl(fcoe_lesb->lesb_err_block); + ctlr_dev->lesb.lesb_fcs_error = + ntohl(fcoe_lesb->lesb_fcs_error); +} +EXPORT_SYMBOL(bnx2fc_ctlr_get_lesb); + +static void bnx2fc_fcf_get_vlan_id(struct fcoe_fcf_device *fcf_dev) +{ + struct fcoe_ctlr_device *ctlr_dev = + fcoe_fcf_dev_to_ctlr_dev(fcf_dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + struct bnx2fc_interface *fcoe = fcoe_ctlr_priv(ctlr); + + fcf_dev->vlan_id = fcoe->vlan_id; +} + static void bnx2fc_clean_rx_queue(struct fc_lport *lp) { struct fcoe_percpu_s *bg; @@ -244,6 +281,7 @@ static int bnx2fc_xmit(struct fc_lport *lport, struct fc_frame *fp) struct sk_buff *skb; struct fc_frame_header *fh; struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; struct bnx2fc_hba *hba; struct fcoe_port *port; struct fcoe_hdr *hp; @@ -256,6 +294,7 @@ static int bnx2fc_xmit(struct fc_lport *lport, struct fc_frame *fp) port = (struct fcoe_port *)lport_priv(lport); interface = port->priv; + ctlr = bnx2fc_to_ctlr(interface); hba = interface->hba; fh = fc_frame_header_get(fp); @@ -268,12 +307,12 @@ static int bnx2fc_xmit(struct fc_lport *lport, struct fc_frame *fp) } if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ)) { - if (!interface->ctlr.sel_fcf) { + if (!ctlr->sel_fcf) { BNX2FC_HBA_DBG(lport, "FCF not selected yet!\n"); kfree_skb(skb); return -EINVAL; } - if (fcoe_ctlr_els_send(&interface->ctlr, lport, skb)) + if (fcoe_ctlr_els_send(ctlr, lport, skb)) return 0; } @@ -346,14 +385,14 @@ static int bnx2fc_xmit(struct fc_lport *lport, struct fc_frame *fp) /* fill up mac and fcoe headers */ eh = eth_hdr(skb); eh->h_proto = htons(ETH_P_FCOE); - if (interface->ctlr.map_dest) + if (ctlr->map_dest) fc_fcoe_set_mac(eh->h_dest, fh->fh_d_id); else /* insert GW address */ - memcpy(eh->h_dest, interface->ctlr.dest_addr, ETH_ALEN); + memcpy(eh->h_dest, ctlr->dest_addr, ETH_ALEN); - if (unlikely(interface->ctlr.flogi_oxid != FC_XID_UNKNOWN)) - memcpy(eh->h_source, interface->ctlr.ctl_src_addr, ETH_ALEN); + if (unlikely(ctlr->flogi_oxid != FC_XID_UNKNOWN)) + memcpy(eh->h_source, ctlr->ctl_src_addr, ETH_ALEN); else memcpy(eh->h_source, port->data_src_addr, ETH_ALEN); @@ -403,6 +442,7 @@ static int bnx2fc_rcv(struct sk_buff *skb, struct net_device *dev, { struct fc_lport *lport; struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; struct fc_frame_header *fh; struct fcoe_rcv_info *fr; struct fcoe_percpu_s *bg; @@ -410,7 +450,8 @@ static int bnx2fc_rcv(struct sk_buff *skb, struct net_device *dev, interface = container_of(ptype, struct bnx2fc_interface, fcoe_packet_type); - lport = interface->ctlr.lp; + ctlr = bnx2fc_to_ctlr(interface); + lport = ctlr->lp; if (unlikely(lport == NULL)) { printk(KERN_ERR PFX "bnx2fc_rcv: lport is NULL\n"); @@ -758,11 +799,13 @@ static int bnx2fc_net_config(struct fc_lport *lport, struct net_device *netdev) { struct bnx2fc_hba *hba; struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; struct fcoe_port *port; u64 wwnn, wwpn; port = lport_priv(lport); interface = port->priv; + ctlr = bnx2fc_to_ctlr(interface); hba = interface->hba; /* require support for get_pauseparam ethtool op. */ @@ -781,13 +824,13 @@ static int bnx2fc_net_config(struct fc_lport *lport, struct net_device *netdev) if (!lport->vport) { if (fcoe_get_wwn(netdev, &wwnn, NETDEV_FCOE_WWNN)) - wwnn = fcoe_wwn_from_mac(interface->ctlr.ctl_src_addr, + wwnn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, 1, 0); BNX2FC_HBA_DBG(lport, "WWNN = 0x%llx\n", wwnn); fc_set_wwnn(lport, wwnn); if (fcoe_get_wwn(netdev, &wwpn, NETDEV_FCOE_WWPN)) - wwpn = fcoe_wwn_from_mac(interface->ctlr.ctl_src_addr, + wwpn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, 2, 0); BNX2FC_HBA_DBG(lport, "WWPN = 0x%llx\n", wwpn); @@ -824,6 +867,7 @@ static void bnx2fc_indicate_netevent(void *context, unsigned long event, struct fc_lport *lport; struct fc_lport *vport; struct bnx2fc_interface *interface, *tmp; + struct fcoe_ctlr *ctlr; int wait_for_upload = 0; u32 link_possible = 1; @@ -874,7 +918,8 @@ static void bnx2fc_indicate_netevent(void *context, unsigned long event, if (interface->hba != hba) continue; - lport = interface->ctlr.lp; + ctlr = bnx2fc_to_ctlr(interface); + lport = ctlr->lp; BNX2FC_HBA_DBG(lport, "netevent handler - event=%s %ld\n", interface->netdev->name, event); @@ -889,8 +934,8 @@ static void bnx2fc_indicate_netevent(void *context, unsigned long event, * on a stale vlan */ if (interface->enabled) - fcoe_ctlr_link_up(&interface->ctlr); - } else if (fcoe_ctlr_link_down(&interface->ctlr)) { + fcoe_ctlr_link_up(ctlr); + } else if (fcoe_ctlr_link_down(ctlr)) { mutex_lock(&lport->lp_mutex); list_for_each_entry(vport, &lport->vports, list) fc_host_port_type(vport->host) = @@ -995,9 +1040,11 @@ static int bnx2fc_fip_recv(struct sk_buff *skb, struct net_device *dev, struct net_device *orig_dev) { struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; interface = container_of(ptype, struct bnx2fc_interface, fip_packet_type); - fcoe_ctlr_recv(&interface->ctlr, skb); + ctlr = bnx2fc_to_ctlr(interface); + fcoe_ctlr_recv(ctlr, skb); return 0; } @@ -1155,6 +1202,7 @@ static int bnx2fc_interface_setup(struct bnx2fc_interface *interface) { struct net_device *netdev = interface->netdev; struct net_device *physdev = interface->hba->phys_dev; + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct netdev_hw_addr *ha; int sel_san_mac = 0; @@ -1169,7 +1217,7 @@ static int bnx2fc_interface_setup(struct bnx2fc_interface *interface) if ((ha->type == NETDEV_HW_ADDR_T_SAN) && (is_valid_ether_addr(ha->addr))) { - memcpy(interface->ctlr.ctl_src_addr, ha->addr, + memcpy(ctlr->ctl_src_addr, ha->addr, ETH_ALEN); sel_san_mac = 1; BNX2FC_MISC_DBG("Found SAN MAC\n"); @@ -1224,19 +1272,23 @@ static void bnx2fc_release_transport(void) static void bnx2fc_interface_release(struct kref *kref) { + struct fcoe_ctlr_device *ctlr_dev; struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; struct net_device *netdev; interface = container_of(kref, struct bnx2fc_interface, kref); BNX2FC_MISC_DBG("Interface is being released\n"); + ctlr = bnx2fc_to_ctlr(interface); + ctlr_dev = fcoe_ctlr_to_ctlr_dev(ctlr); netdev = interface->netdev; /* tear-down FIP controller */ if (test_and_clear_bit(BNX2FC_CTLR_INIT_DONE, &interface->if_flags)) - fcoe_ctlr_destroy(&interface->ctlr); + fcoe_ctlr_destroy(ctlr); - kfree(interface); + fcoe_ctlr_device_delete(ctlr_dev); dev_put(netdev); module_put(THIS_MODULE); @@ -1329,33 +1381,40 @@ struct bnx2fc_interface *bnx2fc_interface_create(struct bnx2fc_hba *hba, struct net_device *netdev, enum fip_state fip_mode) { + struct fcoe_ctlr_device *ctlr_dev; struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; + int size; int rc = 0; - interface = kzalloc(sizeof(*interface), GFP_KERNEL); - if (!interface) { + size = (sizeof(*interface) + sizeof(struct fcoe_ctlr)); + ctlr_dev = fcoe_ctlr_device_add(&netdev->dev, &bnx2fc_fcoe_sysfs_templ, + size); + if (!ctlr_dev) { printk(KERN_ERR PFX "Unable to allocate interface structure\n"); return NULL; } + ctlr = fcoe_ctlr_device_priv(ctlr_dev); + interface = fcoe_ctlr_priv(ctlr); dev_hold(netdev); kref_init(&interface->kref); interface->hba = hba; interface->netdev = netdev; /* Initialize FIP */ - fcoe_ctlr_init(&interface->ctlr, fip_mode); - interface->ctlr.send = bnx2fc_fip_send; - interface->ctlr.update_mac = bnx2fc_update_src_mac; - interface->ctlr.get_src_addr = bnx2fc_get_src_mac; + fcoe_ctlr_init(ctlr, fip_mode); + ctlr->send = bnx2fc_fip_send; + ctlr->update_mac = bnx2fc_update_src_mac; + ctlr->get_src_addr = bnx2fc_get_src_mac; set_bit(BNX2FC_CTLR_INIT_DONE, &interface->if_flags); rc = bnx2fc_interface_setup(interface); if (!rc) return interface; - fcoe_ctlr_destroy(&interface->ctlr); + fcoe_ctlr_destroy(ctlr); dev_put(netdev); - kfree(interface); + fcoe_ctlr_device_delete(ctlr_dev); return NULL; } @@ -1373,6 +1432,7 @@ struct bnx2fc_interface *bnx2fc_interface_create(struct bnx2fc_hba *hba, static struct fc_lport *bnx2fc_if_create(struct bnx2fc_interface *interface, struct device *parent, int npiv) { + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct fc_lport *lport, *n_port; struct fcoe_port *port; struct Scsi_Host *shost; @@ -1383,7 +1443,7 @@ static struct fc_lport *bnx2fc_if_create(struct bnx2fc_interface *interface, blport = kzalloc(sizeof(struct bnx2fc_lport), GFP_KERNEL); if (!blport) { - BNX2FC_HBA_DBG(interface->ctlr.lp, "Unable to alloc blport\n"); + BNX2FC_HBA_DBG(ctlr->lp, "Unable to alloc blport\n"); return NULL; } @@ -1479,7 +1539,8 @@ static void bnx2fc_net_cleanup(struct bnx2fc_interface *interface) static void bnx2fc_interface_cleanup(struct bnx2fc_interface *interface) { - struct fc_lport *lport = interface->ctlr.lp; + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); + struct fc_lport *lport = ctlr->lp; struct fcoe_port *port = lport_priv(lport); struct bnx2fc_hba *hba = interface->hba; @@ -1519,7 +1580,8 @@ static void bnx2fc_if_destroy(struct fc_lport *lport) static void __bnx2fc_destroy(struct bnx2fc_interface *interface) { - struct fc_lport *lport = interface->ctlr.lp; + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); + struct fc_lport *lport = ctlr->lp; struct fcoe_port *port = lport_priv(lport); bnx2fc_interface_cleanup(interface); @@ -1543,13 +1605,15 @@ static int bnx2fc_destroy(struct net_device *netdev) { struct bnx2fc_interface *interface = NULL; struct workqueue_struct *timer_work_queue; + struct fcoe_ctlr *ctlr; int rc = 0; rtnl_lock(); mutex_lock(&bnx2fc_dev_lock); interface = bnx2fc_interface_lookup(netdev); - if (!interface || !interface->ctlr.lp) { + ctlr = bnx2fc_to_ctlr(interface); + if (!interface || !ctlr->lp) { rc = -ENODEV; printk(KERN_ERR PFX "bnx2fc_destroy: interface or lport not found\n"); goto netdev_err; @@ -1646,6 +1710,7 @@ static void bnx2fc_ulp_start(void *handle) { struct bnx2fc_hba *hba = handle; struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; struct fc_lport *lport; mutex_lock(&bnx2fc_dev_lock); @@ -1657,7 +1722,8 @@ static void bnx2fc_ulp_start(void *handle) list_for_each_entry(interface, &if_list, list) { if (interface->hba == hba) { - lport = interface->ctlr.lp; + ctlr = bnx2fc_to_ctlr(interface); + lport = ctlr->lp; /* Kick off Fabric discovery*/ printk(KERN_ERR PFX "ulp_init: start discovery\n"); lport->tt.frame_send = bnx2fc_xmit; @@ -1677,13 +1743,14 @@ static void bnx2fc_port_shutdown(struct fc_lport *lport) static void bnx2fc_stop(struct bnx2fc_interface *interface) { + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct fc_lport *lport; struct fc_lport *vport; if (!test_bit(BNX2FC_FLAG_FW_INIT_DONE, &interface->hba->flags)) return; - lport = interface->ctlr.lp; + lport = ctlr->lp; bnx2fc_port_shutdown(lport); mutex_lock(&lport->lp_mutex); @@ -1692,7 +1759,7 @@ static void bnx2fc_stop(struct bnx2fc_interface *interface) FC_PORTTYPE_UNKNOWN; mutex_unlock(&lport->lp_mutex); fc_host_port_type(lport->host) = FC_PORTTYPE_UNKNOWN; - fcoe_ctlr_link_down(&interface->ctlr); + fcoe_ctlr_link_down(ctlr); fcoe_clean_pending_queue(lport); } @@ -1804,6 +1871,7 @@ exit: static void bnx2fc_start_disc(struct bnx2fc_interface *interface) { + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct fc_lport *lport; int wait_cnt = 0; @@ -1814,18 +1882,18 @@ static void bnx2fc_start_disc(struct bnx2fc_interface *interface) return; } - lport = interface->ctlr.lp; + lport = ctlr->lp; BNX2FC_HBA_DBG(lport, "calling fc_fabric_login\n"); if (!bnx2fc_link_ok(lport) && interface->enabled) { BNX2FC_HBA_DBG(lport, "ctlr_link_up\n"); - fcoe_ctlr_link_up(&interface->ctlr); + fcoe_ctlr_link_up(ctlr); fc_host_port_type(lport->host) = FC_PORTTYPE_NPORT; set_bit(ADAPTER_STATE_READY, &interface->hba->adapter_state); } /* wait for the FCF to be selected before issuing FLOGI */ - while (!interface->ctlr.sel_fcf) { + while (!ctlr->sel_fcf) { msleep(250); /* give up after 3 secs */ if (++wait_cnt > 12) @@ -1889,19 +1957,21 @@ static void bnx2fc_ulp_init(struct cnic_dev *dev) static int bnx2fc_disable(struct net_device *netdev) { struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; int rc = 0; rtnl_lock(); mutex_lock(&bnx2fc_dev_lock); interface = bnx2fc_interface_lookup(netdev); - if (!interface || !interface->ctlr.lp) { + ctlr = bnx2fc_to_ctlr(interface); + if (!interface || !ctlr->lp) { rc = -ENODEV; printk(KERN_ERR PFX "bnx2fc_disable: interface or lport not found\n"); } else { interface->enabled = false; - fcoe_ctlr_link_down(&interface->ctlr); - fcoe_clean_pending_queue(interface->ctlr.lp); + fcoe_ctlr_link_down(ctlr); + fcoe_clean_pending_queue(ctlr->lp); } mutex_unlock(&bnx2fc_dev_lock); @@ -1913,17 +1983,19 @@ static int bnx2fc_disable(struct net_device *netdev) static int bnx2fc_enable(struct net_device *netdev) { struct bnx2fc_interface *interface; + struct fcoe_ctlr *ctlr; int rc = 0; rtnl_lock(); mutex_lock(&bnx2fc_dev_lock); interface = bnx2fc_interface_lookup(netdev); - if (!interface || !interface->ctlr.lp) { + ctlr = bnx2fc_to_ctlr(interface); + if (!interface || !ctlr->lp) { rc = -ENODEV; printk(KERN_ERR PFX "bnx2fc_enable: interface or lport not found\n"); - } else if (!bnx2fc_link_ok(interface->ctlr.lp)) { - fcoe_ctlr_link_up(&interface->ctlr); + } else if (!bnx2fc_link_ok(ctlr->lp)) { + fcoe_ctlr_link_up(ctlr); interface->enabled = true; } @@ -1944,6 +2016,7 @@ static int bnx2fc_enable(struct net_device *netdev) */ static int bnx2fc_create(struct net_device *netdev, enum fip_state fip_mode) { + struct fcoe_ctlr *ctlr; struct bnx2fc_interface *interface; struct bnx2fc_hba *hba; struct net_device *phys_dev; @@ -2010,6 +2083,7 @@ static int bnx2fc_create(struct net_device *netdev, enum fip_state fip_mode) goto ifput_err; } + ctlr = bnx2fc_to_ctlr(interface); interface->vlan_id = vlan_id; interface->vlan_enabled = 1; @@ -2035,10 +2109,10 @@ static int bnx2fc_create(struct net_device *netdev, enum fip_state fip_mode) lport->boot_time = jiffies; /* Make this master N_port */ - interface->ctlr.lp = lport; + ctlr->lp = lport; if (!bnx2fc_link_ok(lport)) { - fcoe_ctlr_link_up(&interface->ctlr); + fcoe_ctlr_link_up(ctlr); fc_host_port_type(lport->host) = FC_PORTTYPE_NPORT; set_bit(ADAPTER_STATE_READY, &interface->hba->adapter_state); } @@ -2439,6 +2513,19 @@ static void __exit bnx2fc_mod_exit(void) module_init(bnx2fc_mod_init); module_exit(bnx2fc_mod_exit); +static struct fcoe_sysfs_function_template bnx2fc_fcoe_sysfs_templ = { + .get_fcoe_ctlr_mode = fcoe_ctlr_get_fip_mode, + .get_fcoe_ctlr_link_fail = bnx2fc_ctlr_get_lesb, + .get_fcoe_ctlr_vlink_fail = bnx2fc_ctlr_get_lesb, + .get_fcoe_ctlr_miss_fka = bnx2fc_ctlr_get_lesb, + .get_fcoe_ctlr_symb_err = bnx2fc_ctlr_get_lesb, + .get_fcoe_ctlr_err_block = bnx2fc_ctlr_get_lesb, + .get_fcoe_ctlr_fcs_error = bnx2fc_ctlr_get_lesb, + + .get_fcoe_fcf_selected = fcoe_fcf_get_selected, + .get_fcoe_fcf_vlan_id = bnx2fc_fcf_get_vlan_id, +}; + static struct fc_function_template bnx2fc_transport_function = { .show_host_node_name = 1, .show_host_port_name = 1, diff --git a/drivers/scsi/bnx2fc/bnx2fc_hwi.c b/drivers/scsi/bnx2fc/bnx2fc_hwi.c index afd570962b8..2ca6bfe4ce5 100644 --- a/drivers/scsi/bnx2fc/bnx2fc_hwi.c +++ b/drivers/scsi/bnx2fc/bnx2fc_hwi.c @@ -167,6 +167,7 @@ int bnx2fc_send_session_ofld_req(struct fcoe_port *port, { struct fc_lport *lport = port->lport; struct bnx2fc_interface *interface = port->priv; + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct bnx2fc_hba *hba = interface->hba; struct kwqe *kwqe_arr[4]; struct fcoe_kwqe_conn_offload1 ofld_req1; @@ -314,13 +315,13 @@ int bnx2fc_send_session_ofld_req(struct fcoe_port *port, ofld_req4.src_mac_addr_mid[1] = port->data_src_addr[2]; ofld_req4.src_mac_addr_hi[0] = port->data_src_addr[1]; ofld_req4.src_mac_addr_hi[1] = port->data_src_addr[0]; - ofld_req4.dst_mac_addr_lo[0] = interface->ctlr.dest_addr[5]; + ofld_req4.dst_mac_addr_lo[0] = ctlr->dest_addr[5]; /* fcf mac */ - ofld_req4.dst_mac_addr_lo[1] = interface->ctlr.dest_addr[4]; - ofld_req4.dst_mac_addr_mid[0] = interface->ctlr.dest_addr[3]; - ofld_req4.dst_mac_addr_mid[1] = interface->ctlr.dest_addr[2]; - ofld_req4.dst_mac_addr_hi[0] = interface->ctlr.dest_addr[1]; - ofld_req4.dst_mac_addr_hi[1] = interface->ctlr.dest_addr[0]; + ofld_req4.dst_mac_addr_lo[1] = ctlr->dest_addr[4]; + ofld_req4.dst_mac_addr_mid[0] = ctlr->dest_addr[3]; + ofld_req4.dst_mac_addr_mid[1] = ctlr->dest_addr[2]; + ofld_req4.dst_mac_addr_hi[0] = ctlr->dest_addr[1]; + ofld_req4.dst_mac_addr_hi[1] = ctlr->dest_addr[0]; ofld_req4.lcq_addr_lo = (u32) tgt->lcq_dma; ofld_req4.lcq_addr_hi = (u32)((u64) tgt->lcq_dma >> 32); @@ -351,6 +352,7 @@ static int bnx2fc_send_session_enable_req(struct fcoe_port *port, { struct kwqe *kwqe_arr[2]; struct bnx2fc_interface *interface = port->priv; + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct bnx2fc_hba *hba = interface->hba; struct fcoe_kwqe_conn_enable_disable enbl_req; struct fc_lport *lport = port->lport; @@ -374,12 +376,12 @@ static int bnx2fc_send_session_enable_req(struct fcoe_port *port, enbl_req.src_mac_addr_hi[1] = port->data_src_addr[0]; memcpy(tgt->src_addr, port->data_src_addr, ETH_ALEN); - enbl_req.dst_mac_addr_lo[0] = interface->ctlr.dest_addr[5]; - enbl_req.dst_mac_addr_lo[1] = interface->ctlr.dest_addr[4]; - enbl_req.dst_mac_addr_mid[0] = interface->ctlr.dest_addr[3]; - enbl_req.dst_mac_addr_mid[1] = interface->ctlr.dest_addr[2]; - enbl_req.dst_mac_addr_hi[0] = interface->ctlr.dest_addr[1]; - enbl_req.dst_mac_addr_hi[1] = interface->ctlr.dest_addr[0]; + enbl_req.dst_mac_addr_lo[0] = ctlr->dest_addr[5]; + enbl_req.dst_mac_addr_lo[1] = ctlr->dest_addr[4]; + enbl_req.dst_mac_addr_mid[0] = ctlr->dest_addr[3]; + enbl_req.dst_mac_addr_mid[1] = ctlr->dest_addr[2]; + enbl_req.dst_mac_addr_hi[0] = ctlr->dest_addr[1]; + enbl_req.dst_mac_addr_hi[1] = ctlr->dest_addr[0]; port_id = fc_host_port_id(lport->host); if (port_id != tgt->sid) { @@ -419,6 +421,7 @@ int bnx2fc_send_session_disable_req(struct fcoe_port *port, struct bnx2fc_rport *tgt) { struct bnx2fc_interface *interface = port->priv; + struct fcoe_ctlr *ctlr = bnx2fc_to_ctlr(interface); struct bnx2fc_hba *hba = interface->hba; struct fcoe_kwqe_conn_enable_disable disable_req; struct kwqe *kwqe_arr[2]; @@ -440,12 +443,12 @@ int bnx2fc_send_session_disable_req(struct fcoe_port *port, disable_req.src_mac_addr_hi[0] = tgt->src_addr[1]; disable_req.src_mac_addr_hi[1] = tgt->src_addr[0]; - disable_req.dst_mac_addr_lo[0] = interface->ctlr.dest_addr[5]; - disable_req.dst_mac_addr_lo[1] = interface->ctlr.dest_addr[4]; - disable_req.dst_mac_addr_mid[0] = interface->ctlr.dest_addr[3]; - disable_req.dst_mac_addr_mid[1] = interface->ctlr.dest_addr[2]; - disable_req.dst_mac_addr_hi[0] = interface->ctlr.dest_addr[1]; - disable_req.dst_mac_addr_hi[1] = interface->ctlr.dest_addr[0]; + disable_req.dst_mac_addr_lo[0] = ctlr->dest_addr[5]; + disable_req.dst_mac_addr_lo[1] = ctlr->dest_addr[4]; + disable_req.dst_mac_addr_mid[0] = ctlr->dest_addr[3]; + disable_req.dst_mac_addr_mid[1] = ctlr->dest_addr[2]; + disable_req.dst_mac_addr_hi[0] = ctlr->dest_addr[1]; + disable_req.dst_mac_addr_hi[1] = ctlr->dest_addr[0]; port_id = tgt->sid; disable_req.s_id[0] = (port_id & 0x000000FF); diff --git a/drivers/scsi/bnx2fc/bnx2fc_io.c b/drivers/scsi/bnx2fc/bnx2fc_io.c index e897ce975bb..4f7453b9e41 100644 --- a/drivers/scsi/bnx2fc/bnx2fc_io.c +++ b/drivers/scsi/bnx2fc/bnx2fc_io.c @@ -810,8 +810,22 @@ retry_tmf: spin_lock_bh(&tgt->tgt_lock); io_req->wait_for_comp = 0; - if (!(test_bit(BNX2FC_FLAG_TM_COMPL, &io_req->req_flags))) + if (!(test_bit(BNX2FC_FLAG_TM_COMPL, &io_req->req_flags))) { set_bit(BNX2FC_FLAG_TM_TIMEOUT, &io_req->req_flags); + if (io_req->on_tmf_queue) { + list_del_init(&io_req->link); + io_req->on_tmf_queue = 0; + } + io_req->wait_for_comp = 1; + bnx2fc_initiate_cleanup(io_req); + spin_unlock_bh(&tgt->tgt_lock); + rc = wait_for_completion_timeout(&io_req->tm_done, + BNX2FC_FW_TIMEOUT); + spin_lock_bh(&tgt->tgt_lock); + io_req->wait_for_comp = 0; + if (!rc) + kref_put(&io_req->refcount, bnx2fc_cmd_release); + } spin_unlock_bh(&tgt->tgt_lock); @@ -1089,6 +1103,48 @@ int bnx2fc_eh_device_reset(struct scsi_cmnd *sc_cmd) return bnx2fc_initiate_tmf(sc_cmd, FCP_TMF_LUN_RESET); } +int bnx2fc_expl_logo(struct fc_lport *lport, struct bnx2fc_cmd *io_req) +{ + struct bnx2fc_rport *tgt = io_req->tgt; + struct fc_rport_priv *rdata = tgt->rdata; + int logo_issued; + int rc = SUCCESS; + int wait_cnt = 0; + + BNX2FC_IO_DBG(io_req, "Expl logo - tgt flags = 0x%lx\n", + tgt->flags); + logo_issued = test_and_set_bit(BNX2FC_FLAG_EXPL_LOGO, + &tgt->flags); + io_req->wait_for_comp = 1; + bnx2fc_initiate_cleanup(io_req); + + spin_unlock_bh(&tgt->tgt_lock); + + wait_for_completion(&io_req->tm_done); + + io_req->wait_for_comp = 0; + /* + * release the reference taken in eh_abort to allow the + * target to re-login after flushing IOs + */ + kref_put(&io_req->refcount, bnx2fc_cmd_release); + + if (!logo_issued) { + clear_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags); + mutex_lock(&lport->disc.disc_mutex); + lport->tt.rport_logoff(rdata); + mutex_unlock(&lport->disc.disc_mutex); + do { + msleep(BNX2FC_RELOGIN_WAIT_TIME); + if (wait_cnt++ > BNX2FC_RELOGIN_WAIT_CNT) { + rc = FAILED; + break; + } + } while (!test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags)); + } + spin_lock_bh(&tgt->tgt_lock); + return rc; +} /** * bnx2fc_eh_abort - eh_abort_handler api to abort an outstanding * SCSI command @@ -1103,10 +1159,7 @@ int bnx2fc_eh_abort(struct scsi_cmnd *sc_cmd) struct fc_rport_libfc_priv *rp = rport->dd_data; struct bnx2fc_cmd *io_req; struct fc_lport *lport; - struct fc_rport_priv *rdata; struct bnx2fc_rport *tgt; - int logo_issued; - int wait_cnt = 0; int rc = FAILED; @@ -1183,58 +1236,31 @@ int bnx2fc_eh_abort(struct scsi_cmnd *sc_cmd) list_add_tail(&io_req->link, &tgt->io_retire_queue); init_completion(&io_req->tm_done); - io_req->wait_for_comp = 1; - if (!test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, &io_req->req_flags)) { - /* Cancel the current timer running on this io_req */ - if (cancel_delayed_work(&io_req->timeout_work)) - kref_put(&io_req->refcount, - bnx2fc_cmd_release); /* drop timer hold */ - set_bit(BNX2FC_FLAG_EH_ABORT, &io_req->req_flags); - rc = bnx2fc_initiate_abts(io_req); - } else { + if (test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, &io_req->req_flags)) { printk(KERN_ERR PFX "eh_abort: io_req (xid = 0x%x) " "already in abts processing\n", io_req->xid); if (cancel_delayed_work(&io_req->timeout_work)) kref_put(&io_req->refcount, bnx2fc_cmd_release); /* drop timer hold */ - bnx2fc_initiate_cleanup(io_req); + rc = bnx2fc_expl_logo(lport, io_req); + goto out; + } + /* Cancel the current timer running on this io_req */ + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + set_bit(BNX2FC_FLAG_EH_ABORT, &io_req->req_flags); + io_req->wait_for_comp = 1; + rc = bnx2fc_initiate_abts(io_req); + if (rc == FAILED) { + bnx2fc_initiate_cleanup(io_req); spin_unlock_bh(&tgt->tgt_lock); - wait_for_completion(&io_req->tm_done); - spin_lock_bh(&tgt->tgt_lock); io_req->wait_for_comp = 0; - rdata = io_req->tgt->rdata; - logo_issued = test_and_set_bit(BNX2FC_FLAG_EXPL_LOGO, - &tgt->flags); - kref_put(&io_req->refcount, bnx2fc_cmd_release); - spin_unlock_bh(&tgt->tgt_lock); - - if (!logo_issued) { - BNX2FC_IO_DBG(io_req, "Expl logo - tgt flags = 0x%lx\n", - tgt->flags); - mutex_lock(&lport->disc.disc_mutex); - lport->tt.rport_logoff(rdata); - mutex_unlock(&lport->disc.disc_mutex); - do { - msleep(BNX2FC_RELOGIN_WAIT_TIME); - /* - * If session not recovered, let SCSI-ml - * escalate error recovery. - */ - if (wait_cnt++ > BNX2FC_RELOGIN_WAIT_CNT) - return FAILED; - } while (!test_bit(BNX2FC_FLAG_SESSION_READY, - &tgt->flags)); - } - return SUCCESS; - } - if (rc == FAILED) { - kref_put(&io_req->refcount, bnx2fc_cmd_release); - spin_unlock_bh(&tgt->tgt_lock); - return rc; + goto done; } spin_unlock_bh(&tgt->tgt_lock); @@ -1247,7 +1273,8 @@ int bnx2fc_eh_abort(struct scsi_cmnd *sc_cmd) /* Let the scsi-ml try to recover this command */ printk(KERN_ERR PFX "abort failed, xid = 0x%x\n", io_req->xid); - rc = FAILED; + rc = bnx2fc_expl_logo(lport, io_req); + goto out; } else { /* * We come here even when there was a race condition @@ -1259,9 +1286,10 @@ int bnx2fc_eh_abort(struct scsi_cmnd *sc_cmd) bnx2fc_scsi_done(io_req, DID_ABORT); kref_put(&io_req->refcount, bnx2fc_cmd_release); } - +done: /* release the reference taken in eh_abort */ kref_put(&io_req->refcount, bnx2fc_cmd_release); +out: spin_unlock_bh(&tgt->tgt_lock); return rc; } diff --git a/drivers/scsi/bnx2fc/bnx2fc_tgt.c b/drivers/scsi/bnx2fc/bnx2fc_tgt.c index c1800b53127..082a25c3117 100644 --- a/drivers/scsi/bnx2fc/bnx2fc_tgt.c +++ b/drivers/scsi/bnx2fc/bnx2fc_tgt.c @@ -185,6 +185,16 @@ void bnx2fc_flush_active_ios(struct bnx2fc_rport *tgt) BUG_ON(rc); } + list_for_each_safe(list, tmp, &tgt->active_tm_queue) { + i++; + io_req = (struct bnx2fc_cmd *)list; + list_del_init(&io_req->link); + io_req->on_tmf_queue = 0; + BNX2FC_IO_DBG(io_req, "tm_queue cleanup\n"); + if (io_req->wait_for_comp) + complete(&io_req->tm_done); + } + list_for_each_safe(list, tmp, &tgt->els_queue) { i++; io_req = (struct bnx2fc_cmd *)list; @@ -213,8 +223,17 @@ void bnx2fc_flush_active_ios(struct bnx2fc_rport *tgt) BNX2FC_IO_DBG(io_req, "retire_queue flush\n"); - if (cancel_delayed_work(&io_req->timeout_work)) + if (cancel_delayed_work(&io_req->timeout_work)) { + if (test_and_clear_bit(BNX2FC_FLAG_EH_ABORT, + &io_req->req_flags)) { + /* Handle eh_abort timeout */ + BNX2FC_IO_DBG(io_req, "eh_abort for IO " + "in retire_q\n"); + if (io_req->wait_for_comp) + complete(&io_req->tm_done); + } kref_put(&io_req->refcount, bnx2fc_cmd_release); + } clear_bit(BNX2FC_FLAG_ISSUE_RRQ, &io_req->req_flags); } diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile index f6d37d0271f..aed0f5db366 100644 --- a/drivers/scsi/fcoe/Makefile +++ b/drivers/scsi/fcoe/Makefile @@ -1,4 +1,4 @@ obj-$(CONFIG_FCOE) += fcoe.o obj-$(CONFIG_LIBFCOE) += libfcoe.o -libfcoe-objs := fcoe_ctlr.o fcoe_transport.o +libfcoe-objs := fcoe_ctlr.o fcoe_transport.o fcoe_sysfs.o diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c index 76e3d0b5bfa..fe30b1b65e1 100644 --- a/drivers/scsi/fcoe/fcoe.c +++ b/drivers/scsi/fcoe/fcoe.c @@ -41,6 +41,7 @@ #include <scsi/fc/fc_encaps.h> #include <scsi/fc/fc_fip.h> +#include <scsi/fc/fc_fcoe.h> #include <scsi/libfc.h> #include <scsi/fc_frame.h> @@ -150,6 +151,21 @@ static int fcoe_vport_create(struct fc_vport *, bool disabled); static int fcoe_vport_disable(struct fc_vport *, bool disable); static void fcoe_set_vport_symbolic_name(struct fc_vport *); static void fcoe_set_port_id(struct fc_lport *, u32, struct fc_frame *); +static void fcoe_ctlr_get_lesb(struct fcoe_ctlr_device *); +static void fcoe_fcf_get_vlan_id(struct fcoe_fcf_device *); + +static struct fcoe_sysfs_function_template fcoe_sysfs_templ = { + .get_fcoe_ctlr_mode = fcoe_ctlr_get_fip_mode, + .get_fcoe_ctlr_link_fail = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_vlink_fail = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_miss_fka = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_symb_err = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_err_block = fcoe_ctlr_get_lesb, + .get_fcoe_ctlr_fcs_error = fcoe_ctlr_get_lesb, + + .get_fcoe_fcf_selected = fcoe_fcf_get_selected, + .get_fcoe_fcf_vlan_id = fcoe_fcf_get_vlan_id, +}; static struct libfc_function_template fcoe_libfc_fcn_templ = { .frame_send = fcoe_xmit, @@ -282,7 +298,7 @@ static struct scsi_host_template fcoe_shost_template = { static int fcoe_interface_setup(struct fcoe_interface *fcoe, struct net_device *netdev) { - struct fcoe_ctlr *fip = &fcoe->ctlr; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); struct netdev_hw_addr *ha; struct net_device *real_dev; u8 flogi_maddr[ETH_ALEN]; @@ -366,7 +382,10 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe, static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev, enum fip_state fip_mode) { + struct fcoe_ctlr_device *ctlr_dev; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; + int size; int err; if (!try_module_get(THIS_MODULE)) { @@ -376,27 +395,32 @@ static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev, goto out; } - fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL); - if (!fcoe) { - FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n"); + size = sizeof(struct fcoe_ctlr) + sizeof(struct fcoe_interface); + ctlr_dev = fcoe_ctlr_device_add(&netdev->dev, &fcoe_sysfs_templ, + size); + if (!ctlr_dev) { + FCOE_DBG("Failed to add fcoe_ctlr_device\n"); fcoe = ERR_PTR(-ENOMEM); goto out_putmod; } + ctlr = fcoe_ctlr_device_priv(ctlr_dev); + fcoe = fcoe_ctlr_priv(ctlr); + dev_hold(netdev); /* * Initialize FIP. */ - fcoe_ctlr_init(&fcoe->ctlr, fip_mode); - fcoe->ctlr.send = fcoe_fip_send; - fcoe->ctlr.update_mac = fcoe_update_src_mac; - fcoe->ctlr.get_src_addr = fcoe_get_src_mac; + fcoe_ctlr_init(ctlr, fip_mode); + ctlr->send = fcoe_fip_send; + ctlr->update_mac = fcoe_update_src_mac; + ctlr->get_src_addr = fcoe_get_src_mac; err = fcoe_interface_setup(fcoe, netdev); if (err) { - fcoe_ctlr_destroy(&fcoe->ctlr); - kfree(fcoe); + fcoe_ctlr_destroy(ctlr); + fcoe_ctlr_device_delete(ctlr_dev); dev_put(netdev); fcoe = ERR_PTR(err); goto out_putmod; @@ -419,7 +443,7 @@ out: static void fcoe_interface_remove(struct fcoe_interface *fcoe) { struct net_device *netdev = fcoe->netdev; - struct fcoe_ctlr *fip = &fcoe->ctlr; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); u8 flogi_maddr[ETH_ALEN]; const struct net_device_ops *ops; @@ -462,7 +486,8 @@ static void fcoe_interface_remove(struct fcoe_interface *fcoe) static void fcoe_interface_cleanup(struct fcoe_interface *fcoe) { struct net_device *netdev = fcoe->netdev; - struct fcoe_ctlr *fip = &fcoe->ctlr; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); + struct fcoe_ctlr_device *ctlr_dev = fcoe_ctlr_to_ctlr_dev(fip); rtnl_lock(); if (!fcoe->removed) @@ -472,8 +497,8 @@ static void fcoe_interface_cleanup(struct fcoe_interface *fcoe) /* Release the self-reference taken during fcoe_interface_create() */ /* tear-down the FCoE controller */ fcoe_ctlr_destroy(fip); - scsi_host_put(fcoe->ctlr.lp->host); - kfree(fcoe); + scsi_host_put(fip->lp->host); + fcoe_ctlr_device_delete(ctlr_dev); dev_put(netdev); module_put(THIS_MODULE); } @@ -493,9 +518,11 @@ static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *netdev, struct net_device *orig_dev) { struct fcoe_interface *fcoe; + struct fcoe_ctlr *ctlr; fcoe = container_of(ptype, struct fcoe_interface, fip_packet_type); - fcoe_ctlr_recv(&fcoe->ctlr, skb); + ctlr = fcoe_to_ctlr(fcoe); + fcoe_ctlr_recv(ctlr, skb); return 0; } @@ -645,11 +672,13 @@ static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev) u32 mfs; u64 wwnn, wwpn; struct fcoe_interface *fcoe; + struct fcoe_ctlr *ctlr; struct fcoe_port *port; /* Setup lport private data to point to fcoe softc */ port = lport_priv(lport); fcoe = port->priv; + ctlr = fcoe_to_ctlr(fcoe); /* * Determine max frame size based on underlying device and optional @@ -676,10 +705,10 @@ static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev) if (!lport->vport) { if (fcoe_get_wwn(netdev, &wwnn, NETDEV_FCOE_WWNN)) - wwnn = fcoe_wwn_from_mac(fcoe->ctlr.ctl_src_addr, 1, 0); + wwnn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, 1, 0); fc_set_wwnn(lport, wwnn); if (fcoe_get_wwn(netdev, &wwpn, NETDEV_FCOE_WWPN)) - wwpn = fcoe_wwn_from_mac(fcoe->ctlr.ctl_src_addr, + wwpn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, 2, 0); fc_set_wwpn(lport, wwpn); } @@ -1056,6 +1085,7 @@ static int fcoe_ddp_done(struct fc_lport *lport, u16 xid) static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, struct device *parent, int npiv) { + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); struct net_device *netdev = fcoe->netdev; struct fc_lport *lport, *n_port; struct fcoe_port *port; @@ -1119,7 +1149,7 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, } /* Initialize the library */ - rc = fcoe_libfc_config(lport, &fcoe->ctlr, &fcoe_libfc_fcn_templ, 1); + rc = fcoe_libfc_config(lport, ctlr, &fcoe_libfc_fcn_templ, 1); if (rc) { FCOE_NETDEV_DBG(netdev, "Could not configure libfc for the " "interface\n"); @@ -1386,6 +1416,7 @@ static int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev, { struct fc_lport *lport; struct fcoe_rcv_info *fr; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fc_frame_header *fh; struct fcoe_percpu_s *fps; @@ -1393,7 +1424,8 @@ static int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev, unsigned int cpu; fcoe = container_of(ptype, struct fcoe_interface, fcoe_packet_type); - lport = fcoe->ctlr.lp; + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; if (unlikely(!lport)) { FCOE_NETDEV_DBG(netdev, "Cannot find hba structure"); goto err2; @@ -1409,8 +1441,8 @@ static int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev, eh = eth_hdr(skb); - if (is_fip_mode(&fcoe->ctlr) && - compare_ether_addr(eh->h_source, fcoe->ctlr.dest_addr)) { + if (is_fip_mode(ctlr) && + compare_ether_addr(eh->h_source, ctlr->dest_addr)) { FCOE_NETDEV_DBG(netdev, "wrong source mac address:%pM\n", eh->h_source); goto err; @@ -1544,6 +1576,7 @@ static int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp) unsigned int elen; /* eth header, may include vlan */ struct fcoe_port *port = lport_priv(lport); struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); u8 sof, eof; struct fcoe_hdr *hp; @@ -1559,7 +1592,7 @@ static int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp) } if (unlikely(fh->fh_type == FC_TYPE_ELS) && - fcoe_ctlr_els_send(&fcoe->ctlr, lport, skb)) + fcoe_ctlr_els_send(ctlr, lport, skb)) return 0; sof = fr_sof(fp); @@ -1623,12 +1656,12 @@ static int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp) /* fill up mac and fcoe headers */ eh = eth_hdr(skb); eh->h_proto = htons(ETH_P_FCOE); - memcpy(eh->h_dest, fcoe->ctlr.dest_addr, ETH_ALEN); - if (fcoe->ctlr.map_dest) + memcpy(eh->h_dest, ctlr->dest_addr, ETH_ALEN); + if (ctlr->map_dest) memcpy(eh->h_dest + 3, fh->fh_d_id, 3); - if (unlikely(fcoe->ctlr.flogi_oxid != FC_XID_UNKNOWN)) - memcpy(eh->h_source, fcoe->ctlr.ctl_src_addr, ETH_ALEN); + if (unlikely(ctlr->flogi_oxid != FC_XID_UNKNOWN)) + memcpy(eh->h_source, ctlr->ctl_src_addr, ETH_ALEN); else memcpy(eh->h_source, port->data_src_addr, ETH_ALEN); @@ -1677,6 +1710,7 @@ static void fcoe_percpu_flush_done(struct sk_buff *skb) static inline int fcoe_filter_frames(struct fc_lport *lport, struct fc_frame *fp) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fc_frame_header *fh; struct sk_buff *skb = (struct sk_buff *)fp; @@ -1698,7 +1732,8 @@ static inline int fcoe_filter_frames(struct fc_lport *lport, return 0; fcoe = ((struct fcoe_port *)lport_priv(lport))->priv; - if (is_fip_mode(&fcoe->ctlr) && fc_frame_payload_op(fp) == ELS_LOGO && + ctlr = fcoe_to_ctlr(fcoe); + if (is_fip_mode(ctlr) && fc_frame_payload_op(fp) == ELS_LOGO && ntoh24(fh->fh_s_id) == FC_FID_FLOGI) { FCOE_DBG("fcoe: dropping FCoE lport LOGO in fip mode\n"); return -EINVAL; @@ -1877,6 +1912,7 @@ static int fcoe_dcb_app_notification(struct notifier_block *notifier, ulong event, void *ptr) { struct dcb_app_type *entry = ptr; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct net_device *netdev; struct fcoe_port *port; @@ -1894,6 +1930,8 @@ static int fcoe_dcb_app_notification(struct notifier_block *notifier, if (!fcoe) return NOTIFY_OK; + ctlr = fcoe_to_ctlr(fcoe); + if (entry->dcbx & DCB_CAP_DCBX_VER_CEE) prio = ffs(entry->app.priority) - 1; else @@ -1904,10 +1942,10 @@ static int fcoe_dcb_app_notification(struct notifier_block *notifier, if (entry->app.protocol == ETH_P_FIP || entry->app.protocol == ETH_P_FCOE) - fcoe->ctlr.priority = prio; + ctlr->priority = prio; if (entry->app.protocol == ETH_P_FCOE) { - port = lport_priv(fcoe->ctlr.lp); + port = lport_priv(ctlr->lp); port->priority = prio; } @@ -1929,6 +1967,7 @@ static int fcoe_device_notification(struct notifier_block *notifier, { struct fc_lport *lport = NULL; struct net_device *netdev = ptr; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fcoe_port *port; struct fcoe_dev_stats *stats; @@ -1938,7 +1977,8 @@ static int fcoe_device_notification(struct notifier_block *notifier, list_for_each_entry(fcoe, &fcoe_hostlist, list) { if (fcoe->netdev == netdev) { - lport = fcoe->ctlr.lp; + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; break; } } @@ -1967,7 +2007,7 @@ static int fcoe_device_notification(struct notifier_block *notifier, break; case NETDEV_UNREGISTER: list_del(&fcoe->list); - port = lport_priv(fcoe->ctlr.lp); + port = lport_priv(ctlr->lp); queue_work(fcoe_wq, &port->destroy_work); goto out; break; @@ -1982,8 +2022,8 @@ static int fcoe_device_notification(struct notifier_block *notifier, fcoe_link_speed_update(lport); if (link_possible && !fcoe_link_ok(lport)) - fcoe_ctlr_link_up(&fcoe->ctlr); - else if (fcoe_ctlr_link_down(&fcoe->ctlr)) { + fcoe_ctlr_link_up(ctlr); + else if (fcoe_ctlr_link_down(ctlr)) { stats = per_cpu_ptr(lport->dev_stats, get_cpu()); stats->LinkFailureCount++; put_cpu(); @@ -2003,6 +2043,7 @@ out: */ static int fcoe_disable(struct net_device *netdev) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; int rc = 0; @@ -2013,8 +2054,9 @@ static int fcoe_disable(struct net_device *netdev) rtnl_unlock(); if (fcoe) { - fcoe_ctlr_link_down(&fcoe->ctlr); - fcoe_clean_pending_queue(fcoe->ctlr.lp); + ctlr = fcoe_to_ctlr(fcoe); + fcoe_ctlr_link_down(ctlr); + fcoe_clean_pending_queue(ctlr->lp); } else rc = -ENODEV; @@ -2032,6 +2074,7 @@ static int fcoe_disable(struct net_device *netdev) */ static int fcoe_enable(struct net_device *netdev) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; int rc = 0; @@ -2040,11 +2083,17 @@ static int fcoe_enable(struct net_device *netdev) fcoe = fcoe_hostlist_lookup_port(netdev); rtnl_unlock(); - if (!fcoe) + if (!fcoe) { rc = -ENODEV; - else if (!fcoe_link_ok(fcoe->ctlr.lp)) - fcoe_ctlr_link_up(&fcoe->ctlr); + goto out; + } + + ctlr = fcoe_to_ctlr(fcoe); + + if (!fcoe_link_ok(ctlr->lp)) + fcoe_ctlr_link_up(ctlr); +out: mutex_unlock(&fcoe_config_mutex); return rc; } @@ -2059,6 +2108,7 @@ static int fcoe_enable(struct net_device *netdev) */ static int fcoe_destroy(struct net_device *netdev) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fc_lport *lport; struct fcoe_port *port; @@ -2071,7 +2121,8 @@ static int fcoe_destroy(struct net_device *netdev) rc = -ENODEV; goto out_nodev; } - lport = fcoe->ctlr.lp; + ctlr = fcoe_to_ctlr(fcoe); + lport = ctlr->lp; port = lport_priv(lport); list_del(&fcoe->list); queue_work(fcoe_wq, &port->destroy_work); @@ -2126,7 +2177,8 @@ static void fcoe_dcb_create(struct fcoe_interface *fcoe) int dcbx; u8 fup, up; struct net_device *netdev = fcoe->realdev; - struct fcoe_port *port = lport_priv(fcoe->ctlr.lp); + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); + struct fcoe_port *port = lport_priv(ctlr->lp); struct dcb_app app = { .priority = 0, .protocol = ETH_P_FCOE @@ -2149,7 +2201,7 @@ static void fcoe_dcb_create(struct fcoe_interface *fcoe) } port->priority = ffs(up) ? ffs(up) - 1 : 0; - fcoe->ctlr.priority = ffs(fup) ? ffs(fup) - 1 : port->priority; + ctlr->priority = ffs(fup) ? ffs(fup) - 1 : port->priority; } #endif } @@ -2166,6 +2218,8 @@ static void fcoe_dcb_create(struct fcoe_interface *fcoe) static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode) { int rc = 0; + struct fcoe_ctlr_device *ctlr_dev; + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; struct fc_lport *lport; @@ -2184,7 +2238,9 @@ static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode) goto out_nodev; } - lport = fcoe_if_create(fcoe, &netdev->dev, 0); + ctlr = fcoe_to_ctlr(fcoe); + ctlr_dev = fcoe_ctlr_to_ctlr_dev(ctlr); + lport = fcoe_if_create(fcoe, &ctlr_dev->dev, 0); if (IS_ERR(lport)) { printk(KERN_ERR "fcoe: Failed to create interface (%s)\n", netdev->name); @@ -2195,7 +2251,7 @@ static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode) } /* Make this the "master" N_Port */ - fcoe->ctlr.lp = lport; + ctlr->lp = lport; /* setup DCB priority attributes. */ fcoe_dcb_create(fcoe); @@ -2208,7 +2264,7 @@ static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode) fc_fabric_login(lport); if (!fcoe_link_ok(lport)) { rtnl_unlock(); - fcoe_ctlr_link_up(&fcoe->ctlr); + fcoe_ctlr_link_up(ctlr); mutex_unlock(&fcoe_config_mutex); return rc; } @@ -2320,11 +2376,12 @@ static int fcoe_reset(struct Scsi_Host *shost) struct fc_lport *lport = shost_priv(shost); struct fcoe_port *port = lport_priv(lport); struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); - fcoe_ctlr_link_down(&fcoe->ctlr); - fcoe_clean_pending_queue(fcoe->ctlr.lp); - if (!fcoe_link_ok(fcoe->ctlr.lp)) - fcoe_ctlr_link_up(&fcoe->ctlr); + fcoe_ctlr_link_down(ctlr); + fcoe_clean_pending_queue(ctlr->lp); + if (!fcoe_link_ok(ctlr->lp)) + fcoe_ctlr_link_up(ctlr); return 0; } @@ -2359,10 +2416,12 @@ fcoe_hostlist_lookup_port(const struct net_device *netdev) */ static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev) { + struct fcoe_ctlr *ctlr; struct fcoe_interface *fcoe; fcoe = fcoe_hostlist_lookup_port(netdev); - return (fcoe) ? fcoe->ctlr.lp : NULL; + ctlr = fcoe_to_ctlr(fcoe); + return (fcoe) ? ctlr->lp : NULL; } /** @@ -2466,6 +2525,7 @@ module_init(fcoe_init); static void __exit fcoe_exit(void) { struct fcoe_interface *fcoe, *tmp; + struct fcoe_ctlr *ctlr; struct fcoe_port *port; unsigned int cpu; @@ -2477,7 +2537,8 @@ static void __exit fcoe_exit(void) rtnl_lock(); list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list) { list_del(&fcoe->list); - port = lport_priv(fcoe->ctlr.lp); + ctlr = fcoe_to_ctlr(fcoe); + port = lport_priv(ctlr->lp); queue_work(fcoe_wq, &port->destroy_work); } rtnl_unlock(); @@ -2573,7 +2634,7 @@ static struct fc_seq *fcoe_elsct_send(struct fc_lport *lport, u32 did, { struct fcoe_port *port = lport_priv(lport); struct fcoe_interface *fcoe = port->priv; - struct fcoe_ctlr *fip = &fcoe->ctlr; + struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe); struct fc_frame_header *fh = fc_frame_header_get(fp); switch (op) { @@ -2730,6 +2791,40 @@ static void fcoe_get_lesb(struct fc_lport *lport, __fcoe_get_lesb(lport, fc_lesb, netdev); } +static void fcoe_ctlr_get_lesb(struct fcoe_ctlr_device *ctlr_dev) +{ + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev); + struct net_device *netdev = fcoe_netdev(fip->lp); + struct fcoe_fc_els_lesb *fcoe_lesb; + struct fc_els_lesb fc_lesb; + + __fcoe_get_lesb(fip->lp, &fc_lesb, netdev); + fcoe_lesb = (struct fcoe_fc_els_lesb *)(&fc_lesb); + + ctlr_dev->lesb.lesb_link_fail = + ntohl(fcoe_lesb->lesb_link_fail); + ctlr_dev->lesb.lesb_vlink_fail = + ntohl(fcoe_lesb->lesb_vlink_fail); + ctlr_dev->lesb.lesb_miss_fka = + ntohl(fcoe_lesb->lesb_miss_fka); + ctlr_dev->lesb.lesb_symb_err = + ntohl(fcoe_lesb->lesb_symb_err); + ctlr_dev->lesb.lesb_err_block = + ntohl(fcoe_lesb->lesb_err_block); + ctlr_dev->lesb.lesb_fcs_error = + ntohl(fcoe_lesb->lesb_fcs_error); +} + +static void fcoe_fcf_get_vlan_id(struct fcoe_fcf_device *fcf_dev) +{ + struct fcoe_ctlr_device *ctlr_dev = + fcoe_fcf_dev_to_ctlr_dev(fcf_dev); + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + struct fcoe_interface *fcoe = fcoe_ctlr_priv(ctlr); + + fcf_dev->vlan_id = vlan_dev_vlan_id(fcoe->netdev); +} + /** * fcoe_set_port_id() - Callback from libfc when Port_ID is set. * @lport: the local port @@ -2747,7 +2842,8 @@ static void fcoe_set_port_id(struct fc_lport *lport, { struct fcoe_port *port = lport_priv(lport); struct fcoe_interface *fcoe = port->priv; + struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe); if (fp && fc_frame_payload_op(fp) == ELS_FLOGI) - fcoe_ctlr_recv_flogi(&fcoe->ctlr, lport, fp); + fcoe_ctlr_recv_flogi(ctlr, lport, fp); } diff --git a/drivers/scsi/fcoe/fcoe.h b/drivers/scsi/fcoe/fcoe.h index 96ac938d39c..a624add4f8e 100644 --- a/drivers/scsi/fcoe/fcoe.h +++ b/drivers/scsi/fcoe/fcoe.h @@ -68,7 +68,6 @@ do { \ * @netdev: The associated net device * @fcoe_packet_type: FCoE packet type * @fip_packet_type: FIP packet type - * @ctlr: The FCoE controller (for FIP) * @oem: The offload exchange manager for all local port * instances associated with this port * @removed: Indicates fcoe interface removed from net device @@ -80,12 +79,15 @@ struct fcoe_interface { struct net_device *realdev; struct packet_type fcoe_packet_type; struct packet_type fip_packet_type; - struct fcoe_ctlr ctlr; struct fc_exch_mgr *oem; u8 removed; }; -#define fcoe_from_ctlr(fip) container_of(fip, struct fcoe_interface, ctlr) +#define fcoe_to_ctlr(x) \ + (struct fcoe_ctlr *)(((struct fcoe_ctlr *)(x)) - 1) + +#define fcoe_from_ctlr(x) \ + ((struct fcoe_interface *)((x) + 1)) /** * fcoe_netdev() - Return the net device associated with a local port diff --git a/drivers/scsi/fcoe/fcoe_ctlr.c b/drivers/scsi/fcoe/fcoe_ctlr.c index 5a4c7250aa7..d68d57241ee 100644 --- a/drivers/scsi/fcoe/fcoe_ctlr.c +++ b/drivers/scsi/fcoe/fcoe_ctlr.c @@ -160,6 +160,76 @@ void fcoe_ctlr_init(struct fcoe_ctlr *fip, enum fip_state mode) } EXPORT_SYMBOL(fcoe_ctlr_init); +static int fcoe_sysfs_fcf_add(struct fcoe_fcf *new) +{ + struct fcoe_ctlr *fip = new->fip; + struct fcoe_ctlr_device *ctlr_dev = fcoe_ctlr_to_ctlr_dev(fip); + struct fcoe_fcf_device temp, *fcf_dev; + int rc = 0; + + LIBFCOE_FIP_DBG(fip, "New FCF fab %16.16llx mac %pM\n", + new->fabric_name, new->fcf_mac); + + mutex_lock(&ctlr_dev->lock); + + temp.fabric_name = new->fabric_name; + temp.switch_name = new->switch_name; + temp.fc_map = new->fc_map; + temp.vfid = new->vfid; + memcpy(temp.mac, new->fcf_mac, ETH_ALEN); + temp.priority = new->pri; + temp.fka_period = new->fka_period; + temp.selected = 0; /* default to unselected */ + + fcf_dev = fcoe_fcf_device_add(ctlr_dev, &temp); + if (unlikely(!fcf_dev)) { + rc = -ENOMEM; + goto out; + } + + /* + * The fcoe_sysfs layer can return a CONNECTED fcf that + * has a priv (fcf was never deleted) or a CONNECTED fcf + * that doesn't have a priv (fcf was deleted). However, + * libfcoe will always delete FCFs before trying to add + * them. This is ensured because both recv_adv and + * age_fcfs are protected by the the fcoe_ctlr's mutex. + * This means that we should never get a FCF with a + * non-NULL priv pointer. + */ + BUG_ON(fcf_dev->priv); + + fcf_dev->priv = new; + new->fcf_dev = fcf_dev; + + list_add(&new->list, &fip->fcfs); + fip->fcf_count++; + +out: + mutex_unlock(&ctlr_dev->lock); + return rc; +} + +static void fcoe_sysfs_fcf_del(struct fcoe_fcf *new) +{ + struct fcoe_ctlr *fip = new->fip; + struct fcoe_ctlr_device *ctlr_dev = fcoe_ctlr_to_ctlr_dev(fip); + struct fcoe_fcf_device *fcf_dev; + + list_del(&new->list); + fip->fcf_count--; + + mutex_lock(&ctlr_dev->lock); + + fcf_dev = fcoe_fcf_to_fcf_dev(new); + WARN_ON(!fcf_dev); + new->fcf_dev = NULL; + fcoe_fcf_device_delete(fcf_dev); + kfree(new); + + mutex_unlock(&ctlr_dev->lock); +} + /** * fcoe_ctlr_reset_fcfs() - Reset and free all FCFs for a controller * @fip: The FCoE controller whose FCFs are to be reset @@ -173,10 +243,10 @@ static void fcoe_ctlr_reset_fcfs(struct fcoe_ctlr *fip) fip->sel_fcf = NULL; list_for_each_entry_safe(fcf, next, &fip->fcfs, list) { - list_del(&fcf->list); - kfree(fcf); + fcoe_sysfs_fcf_del(fcf); } - fip->fcf_count = 0; + WARN_ON(fip->fcf_count); + fip->sel_time = 0; } @@ -717,8 +787,11 @@ static unsigned long fcoe_ctlr_age_fcfs(struct fcoe_ctlr *fip) unsigned long next_timer = jiffies + msecs_to_jiffies(FIP_VN_KA_PERIOD); unsigned long deadline; unsigned long sel_time = 0; + struct list_head del_list; struct fcoe_dev_stats *stats; + INIT_LIST_HEAD(&del_list); + stats = per_cpu_ptr(fip->lp->dev_stats, get_cpu()); list_for_each_entry_safe(fcf, next, &fip->fcfs, list) { @@ -739,10 +812,13 @@ static unsigned long fcoe_ctlr_age_fcfs(struct fcoe_ctlr *fip) if (time_after_eq(jiffies, deadline)) { if (fip->sel_fcf == fcf) fip->sel_fcf = NULL; + /* + * Move to delete list so we can call + * fcoe_sysfs_fcf_del (which can sleep) + * after the put_cpu(). + */ list_del(&fcf->list); - WARN_ON(!fip->fcf_count); - fip->fcf_count--; - kfree(fcf); + list_add(&fcf->list, &del_list); stats->VLinkFailureCount++; } else { if (time_after(next_timer, deadline)) @@ -753,6 +829,12 @@ static unsigned long fcoe_ctlr_age_fcfs(struct fcoe_ctlr *fip) } } put_cpu(); + + list_for_each_entry_safe(fcf, next, &del_list, list) { + /* Removes fcf from current list */ + fcoe_sysfs_fcf_del(fcf); + } + if (sel_time && !fip->sel_fcf && !fip->sel_time) { sel_time += msecs_to_jiffies(FCOE_CTLR_START_DELAY); fip->sel_time = sel_time; @@ -903,23 +985,23 @@ static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb) { struct fcoe_fcf *fcf; struct fcoe_fcf new; - struct fcoe_fcf *found; unsigned long sol_tov = msecs_to_jiffies(FCOE_CTRL_SOL_TOV); int first = 0; int mtu_valid; + int found = 0; + int rc = 0; if (fcoe_ctlr_parse_adv(fip, skb, &new)) return; mutex_lock(&fip->ctlr_mutex); first = list_empty(&fip->fcfs); - found = NULL; list_for_each_entry(fcf, &fip->fcfs, list) { if (fcf->switch_name == new.switch_name && fcf->fabric_name == new.fabric_name && fcf->fc_map == new.fc_map && compare_ether_addr(fcf->fcf_mac, new.fcf_mac) == 0) { - found = fcf; + found = 1; break; } } @@ -931,9 +1013,16 @@ static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb) if (!fcf) goto out; - fip->fcf_count++; memcpy(fcf, &new, sizeof(new)); - list_add(&fcf->list, &fip->fcfs); + fcf->fip = fip; + rc = fcoe_sysfs_fcf_add(fcf); + if (rc) { + printk(KERN_ERR "Failed to allocate sysfs instance " + "for FCF, fab %16.16llx mac %pM\n", + new.fabric_name, new.fcf_mac); + kfree(fcf); + goto out; + } } else { /* * Update the FCF's keep-alive descriptor flags. @@ -954,6 +1043,7 @@ static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb) fcf->fka_period = new.fka_period; memcpy(fcf->fcf_mac, new.fcf_mac, ETH_ALEN); } + mtu_valid = fcoe_ctlr_mtu_valid(fcf); fcf->time = jiffies; if (!found) @@ -996,6 +1086,7 @@ static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb) time_before(fip->sel_time, fip->timer.expires)) mod_timer(&fip->timer, fip->sel_time); } + out: mutex_unlock(&fip->ctlr_mutex); } @@ -2718,9 +2809,9 @@ unlock: /** * fcoe_libfc_config() - Sets up libfc related properties for local port - * @lp: The local port to configure libfc for - * @fip: The FCoE controller in use by the local port - * @tt: The libfc function template + * @lport: The local port to configure libfc for + * @fip: The FCoE controller in use by the local port + * @tt: The libfc function template * @init_fcp: If non-zero, the FCP portion of libfc should be initialized * * Returns : 0 for success @@ -2753,3 +2844,43 @@ int fcoe_libfc_config(struct fc_lport *lport, struct fcoe_ctlr *fip, return 0; } EXPORT_SYMBOL_GPL(fcoe_libfc_config); + +void fcoe_fcf_get_selected(struct fcoe_fcf_device *fcf_dev) +{ + struct fcoe_ctlr_device *ctlr_dev = fcoe_fcf_dev_to_ctlr_dev(fcf_dev); + struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev); + struct fcoe_fcf *fcf; + + mutex_lock(&fip->ctlr_mutex); + mutex_lock(&ctlr_dev->lock); + + fcf = fcoe_fcf_device_priv(fcf_dev); + if (fcf) + fcf_dev->selected = (fcf == fip->sel_fcf) ? 1 : 0; + else + fcf_dev->selected = 0; + + mutex_unlock(&ctlr_dev->lock); + mutex_unlock(&fip->ctlr_mutex); +} +EXPORT_SYMBOL(fcoe_fcf_get_selected); + +void fcoe_ctlr_get_fip_mode(struct fcoe_ctlr_device *ctlr_dev) +{ + struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev); + + mutex_lock(&ctlr->ctlr_mutex); + switch (ctlr->mode) { + case FIP_MODE_FABRIC: + ctlr_dev->mode = FIP_CONN_TYPE_FABRIC; + break; + case FIP_MODE_VN2VN: + ctlr_dev->mode = FIP_CONN_TYPE_VN2VN; + break; + default: + ctlr_dev->mode = FIP_CONN_TYPE_UNKNOWN; + break; + } + mutex_unlock(&ctlr->ctlr_mutex); +} +EXPORT_SYMBOL(fcoe_ctlr_get_fip_mode); diff --git a/drivers/scsi/fcoe/fcoe_sysfs.c b/drivers/scsi/fcoe/fcoe_sysfs.c new file mode 100644 index 00000000000..2bc163198d3 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_sysfs.c @@ -0,0 +1,832 @@ +/* + * Copyright(c) 2011 - 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/etherdevice.h> + +#include <scsi/fcoe_sysfs.h> + +static atomic_t ctlr_num; +static atomic_t fcf_num; + +/* + * fcoe_fcf_dev_loss_tmo: the default number of seconds that fcoe sysfs + * should insulate the loss of a fcf. + */ +static unsigned int fcoe_fcf_dev_loss_tmo = 1800; /* seconds */ + +module_param_named(fcf_dev_loss_tmo, fcoe_fcf_dev_loss_tmo, + uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fcf_dev_loss_tmo, + "Maximum number of seconds that libfcoe should" + " insulate the loss of a fcf. Once this value is" + " exceeded, the fcf is removed."); + +/* + * These are used by the fcoe_*_show_function routines, they + * are intentionally placed in the .c file as they're not intended + * for use throughout the code. + */ +#define fcoe_ctlr_id(x) \ + ((x)->id) +#define fcoe_ctlr_work_q_name(x) \ + ((x)->work_q_name) +#define fcoe_ctlr_work_q(x) \ + ((x)->work_q) +#define fcoe_ctlr_devloss_work_q_name(x) \ + ((x)->devloss_work_q_name) +#define fcoe_ctlr_devloss_work_q(x) \ + ((x)->devloss_work_q) +#define fcoe_ctlr_mode(x) \ + ((x)->mode) +#define fcoe_ctlr_fcf_dev_loss_tmo(x) \ + ((x)->fcf_dev_loss_tmo) +#define fcoe_ctlr_link_fail(x) \ + ((x)->lesb.lesb_link_fail) +#define fcoe_ctlr_vlink_fail(x) \ + ((x)->lesb.lesb_vlink_fail) +#define fcoe_ctlr_miss_fka(x) \ + ((x)->lesb.lesb_miss_fka) +#define fcoe_ctlr_symb_err(x) \ + ((x)->lesb.lesb_symb_err) +#define fcoe_ctlr_err_block(x) \ + ((x)->lesb.lesb_err_block) +#define fcoe_ctlr_fcs_error(x) \ + ((x)->lesb.lesb_fcs_error) +#define fcoe_fcf_state(x) \ + ((x)->state) +#define fcoe_fcf_fabric_name(x) \ + ((x)->fabric_name) +#define fcoe_fcf_switch_name(x) \ + ((x)->switch_name) +#define fcoe_fcf_fc_map(x) \ + ((x)->fc_map) +#define fcoe_fcf_vfid(x) \ + ((x)->vfid) +#define fcoe_fcf_mac(x) \ + ((x)->mac) +#define fcoe_fcf_priority(x) \ + ((x)->priority) +#define fcoe_fcf_fka_period(x) \ + ((x)->fka_period) +#define fcoe_fcf_dev_loss_tmo(x) \ + ((x)->dev_loss_tmo) +#define fcoe_fcf_selected(x) \ + ((x)->selected) +#define fcoe_fcf_vlan_id(x) \ + ((x)->vlan_id) + +/* + * dev_loss_tmo attribute + */ +static int fcoe_str_to_dev_loss(const char *buf, unsigned long *val) +{ + int ret; + + ret = kstrtoul(buf, 0, val); + if (ret || *val < 0) + return -EINVAL; + /* + * Check for overflow; dev_loss_tmo is u32 + */ + if (*val > UINT_MAX) + return -EINVAL; + + return 0; +} + +static int fcoe_fcf_set_dev_loss_tmo(struct fcoe_fcf_device *fcf, + unsigned long val) +{ + if ((fcf->state == FCOE_FCF_STATE_UNKNOWN) || + (fcf->state == FCOE_FCF_STATE_DISCONNECTED) || + (fcf->state == FCOE_FCF_STATE_DELETED)) + return -EBUSY; + /* + * Check for overflow; dev_loss_tmo is u32 + */ + if (val > UINT_MAX) + return -EINVAL; + + fcoe_fcf_dev_loss_tmo(fcf) = val; + return 0; +} + +#define FCOE_DEVICE_ATTR(_prefix, _name, _mode, _show, _store) \ +struct device_attribute device_attr_fcoe_##_prefix##_##_name = \ + __ATTR(_name, _mode, _show, _store) + +#define fcoe_ctlr_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); \ + if (ctlr->f->get_fcoe_ctlr_##field) \ + ctlr->f->get_fcoe_ctlr_##field(ctlr); \ + return snprintf(buf, sz, format_string, \ + cast fcoe_ctlr_##field(ctlr)); \ +} + +#define fcoe_fcf_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_fcf_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); \ + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); \ + if (ctlr->f->get_fcoe_fcf_##field) \ + ctlr->f->get_fcoe_fcf_##field(fcf); \ + return snprintf(buf, sz, format_string, \ + cast fcoe_fcf_##field(fcf)); \ +} + +#define fcoe_ctlr_private_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); \ + return snprintf(buf, sz, format_string, cast fcoe_ctlr_##field(ctlr)); \ +} + +#define fcoe_fcf_private_show_function(field, format_string, sz, cast) \ +static ssize_t show_fcoe_fcf_device_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); \ + return snprintf(buf, sz, format_string, cast fcoe_fcf_##field(fcf)); \ +} + +#define fcoe_ctlr_private_rd_attr(field, format_string, sz) \ + fcoe_ctlr_private_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ + show_fcoe_ctlr_device_##field, NULL) + +#define fcoe_ctlr_rd_attr(field, format_string, sz) \ + fcoe_ctlr_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ + show_fcoe_ctlr_device_##field, NULL) + +#define fcoe_fcf_rd_attr(field, format_string, sz) \ + fcoe_fcf_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ + show_fcoe_fcf_device_##field, NULL) + +#define fcoe_fcf_private_rd_attr(field, format_string, sz) \ + fcoe_fcf_private_show_function(field, format_string, sz, ) \ + static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ + show_fcoe_fcf_device_##field, NULL) + +#define fcoe_ctlr_private_rd_attr_cast(field, format_string, sz, cast) \ + fcoe_ctlr_private_show_function(field, format_string, sz, (cast)) \ + static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO, \ + show_fcoe_ctlr_device_##field, NULL) + +#define fcoe_fcf_private_rd_attr_cast(field, format_string, sz, cast) \ + fcoe_fcf_private_show_function(field, format_string, sz, (cast)) \ + static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO, \ + show_fcoe_fcf_device_##field, NULL) + +#define fcoe_enum_name_search(title, table_type, table) \ +static const char *get_fcoe_##title##_name(enum table_type table_key) \ +{ \ + int i; \ + char *name = NULL; \ + \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ + if (table[i].value == table_key) { \ + name = table[i].name; \ + break; \ + } \ + } \ + return name; \ +} + +static struct { + enum fcf_state value; + char *name; +} fcf_state_names[] = { + { FCOE_FCF_STATE_UNKNOWN, "Unknown" }, + { FCOE_FCF_STATE_DISCONNECTED, "Disconnected" }, + { FCOE_FCF_STATE_CONNECTED, "Connected" }, +}; +fcoe_enum_name_search(fcf_state, fcf_state, fcf_state_names) +#define FCOE_FCF_STATE_MAX_NAMELEN 50 + +static ssize_t show_fcf_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); + const char *name; + name = get_fcoe_fcf_state_name(fcf->state); + if (!name) + return -EINVAL; + return snprintf(buf, FCOE_FCF_STATE_MAX_NAMELEN, "%s\n", name); +} +static FCOE_DEVICE_ATTR(fcf, state, S_IRUGO, show_fcf_state, NULL); + +static struct { + enum fip_conn_type value; + char *name; +} fip_conn_type_names[] = { + { FIP_CONN_TYPE_UNKNOWN, "Unknown" }, + { FIP_CONN_TYPE_FABRIC, "Fabric" }, + { FIP_CONN_TYPE_VN2VN, "VN2VN" }, +}; +fcoe_enum_name_search(ctlr_mode, fip_conn_type, fip_conn_type_names) +#define FCOE_CTLR_MODE_MAX_NAMELEN 50 + +static ssize_t show_ctlr_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + const char *name; + + if (ctlr->f->get_fcoe_ctlr_mode) + ctlr->f->get_fcoe_ctlr_mode(ctlr); + + name = get_fcoe_ctlr_mode_name(ctlr->mode); + if (!name) + return -EINVAL; + return snprintf(buf, FCOE_CTLR_MODE_MAX_NAMELEN, + "%s\n", name); +} +static FCOE_DEVICE_ATTR(ctlr, mode, S_IRUGO, + show_ctlr_mode, NULL); + +static ssize_t +store_private_fcoe_ctlr_fcf_dev_loss_tmo(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + struct fcoe_fcf_device *fcf; + unsigned long val; + int rc; + + rc = fcoe_str_to_dev_loss(buf, &val); + if (rc) + return rc; + + fcoe_ctlr_fcf_dev_loss_tmo(ctlr) = val; + mutex_lock(&ctlr->lock); + list_for_each_entry(fcf, &ctlr->fcfs, peers) + fcoe_fcf_set_dev_loss_tmo(fcf, val); + mutex_unlock(&ctlr->lock); + return count; +} +fcoe_ctlr_private_show_function(fcf_dev_loss_tmo, "%d\n", 20, ); +static FCOE_DEVICE_ATTR(ctlr, fcf_dev_loss_tmo, S_IRUGO | S_IWUSR, + show_fcoe_ctlr_device_fcf_dev_loss_tmo, + store_private_fcoe_ctlr_fcf_dev_loss_tmo); + +/* Link Error Status Block (LESB) */ +fcoe_ctlr_rd_attr(link_fail, "%u\n", 20); +fcoe_ctlr_rd_attr(vlink_fail, "%u\n", 20); +fcoe_ctlr_rd_attr(miss_fka, "%u\n", 20); +fcoe_ctlr_rd_attr(symb_err, "%u\n", 20); +fcoe_ctlr_rd_attr(err_block, "%u\n", 20); +fcoe_ctlr_rd_attr(fcs_error, "%u\n", 20); + +fcoe_fcf_private_rd_attr_cast(fabric_name, "0x%llx\n", 20, unsigned long long); +fcoe_fcf_private_rd_attr_cast(switch_name, "0x%llx\n", 20, unsigned long long); +fcoe_fcf_private_rd_attr(priority, "%u\n", 20); +fcoe_fcf_private_rd_attr(fc_map, "0x%x\n", 20); +fcoe_fcf_private_rd_attr(vfid, "%u\n", 20); +fcoe_fcf_private_rd_attr(mac, "%pM\n", 20); +fcoe_fcf_private_rd_attr(fka_period, "%u\n", 20); +fcoe_fcf_rd_attr(selected, "%u\n", 20); +fcoe_fcf_rd_attr(vlan_id, "%u\n", 20); + +fcoe_fcf_private_show_function(dev_loss_tmo, "%d\n", 20, ) +static ssize_t +store_fcoe_fcf_dev_loss_tmo(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); + unsigned long val; + int rc; + + rc = fcoe_str_to_dev_loss(buf, &val); + if (rc) + return rc; + + rc = fcoe_fcf_set_dev_loss_tmo(fcf, val); + if (rc) + return rc; + return count; +} +static FCOE_DEVICE_ATTR(fcf, dev_loss_tmo, S_IRUGO | S_IWUSR, + show_fcoe_fcf_device_dev_loss_tmo, + store_fcoe_fcf_dev_loss_tmo); + +static struct attribute *fcoe_ctlr_lesb_attrs[] = { + &device_attr_fcoe_ctlr_link_fail.attr, + &device_attr_fcoe_ctlr_vlink_fail.attr, + &device_attr_fcoe_ctlr_miss_fka.attr, + &device_attr_fcoe_ctlr_symb_err.attr, + &device_attr_fcoe_ctlr_err_block.attr, + &device_attr_fcoe_ctlr_fcs_error.attr, + NULL, +}; + +static struct attribute_group fcoe_ctlr_lesb_attr_group = { + .name = "lesb", + .attrs = fcoe_ctlr_lesb_attrs, +}; + +static struct attribute *fcoe_ctlr_attrs[] = { + &device_attr_fcoe_ctlr_fcf_dev_loss_tmo.attr, + &device_attr_fcoe_ctlr_mode.attr, + NULL, +}; + +static struct attribute_group fcoe_ctlr_attr_group = { + .attrs = fcoe_ctlr_attrs, +}; + +static const struct attribute_group *fcoe_ctlr_attr_groups[] = { + &fcoe_ctlr_attr_group, + &fcoe_ctlr_lesb_attr_group, + NULL, +}; + +static struct attribute *fcoe_fcf_attrs[] = { + &device_attr_fcoe_fcf_fabric_name.attr, + &device_attr_fcoe_fcf_switch_name.attr, + &device_attr_fcoe_fcf_dev_loss_tmo.attr, + &device_attr_fcoe_fcf_fc_map.attr, + &device_attr_fcoe_fcf_vfid.attr, + &device_attr_fcoe_fcf_mac.attr, + &device_attr_fcoe_fcf_priority.attr, + &device_attr_fcoe_fcf_fka_period.attr, + &device_attr_fcoe_fcf_state.attr, + &device_attr_fcoe_fcf_selected.attr, + &device_attr_fcoe_fcf_vlan_id.attr, + NULL +}; + +static struct attribute_group fcoe_fcf_attr_group = { + .attrs = fcoe_fcf_attrs, +}; + +static const struct attribute_group *fcoe_fcf_attr_groups[] = { + &fcoe_fcf_attr_group, + NULL, +}; + +struct bus_type fcoe_bus_type; + +static int fcoe_bus_match(struct device *dev, + struct device_driver *drv) +{ + if (dev->bus == &fcoe_bus_type) + return 1; + return 0; +} + +/** + * fcoe_ctlr_device_release() - Release the FIP ctlr memory + * @dev: Pointer to the FIP ctlr's embedded device + * + * Called when the last FIP ctlr reference is released. + */ +static void fcoe_ctlr_device_release(struct device *dev) +{ + struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev); + kfree(ctlr); +} + +/** + * fcoe_fcf_device_release() - Release the FIP fcf memory + * @dev: Pointer to the fcf's embedded device + * + * Called when the last FIP fcf reference is released. + */ +static void fcoe_fcf_device_release(struct device *dev) +{ + struct fcoe_fcf_device *fcf = dev_to_fcf(dev); + kfree(fcf); +} + +struct device_type fcoe_ctlr_device_type = { + .name = "fcoe_ctlr", + .groups = fcoe_ctlr_attr_groups, + .release = fcoe_ctlr_device_release, +}; + +struct device_type fcoe_fcf_device_type = { + .name = "fcoe_fcf", + .groups = fcoe_fcf_attr_groups, + .release = fcoe_fcf_device_release, +}; + +struct bus_type fcoe_bus_type = { + .name = "fcoe", + .match = &fcoe_bus_match, +}; + +/** + * fcoe_ctlr_device_flush_work() - Flush a FIP ctlr's workqueue + * @ctlr: Pointer to the FIP ctlr whose workqueue is to be flushed + */ +void fcoe_ctlr_device_flush_work(struct fcoe_ctlr_device *ctlr) +{ + if (!fcoe_ctlr_work_q(ctlr)) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to flush work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + return; + } + + flush_workqueue(fcoe_ctlr_work_q(ctlr)); +} + +/** + * fcoe_ctlr_device_queue_work() - Schedule work for a FIP ctlr's workqueue + * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue + * @work: Work to queue for execution + * + * Return value: + * 1 on success / 0 already queued / < 0 for error + */ +int fcoe_ctlr_device_queue_work(struct fcoe_ctlr_device *ctlr, + struct work_struct *work) +{ + if (unlikely(!fcoe_ctlr_work_q(ctlr))) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to queue work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + + return -EINVAL; + } + + return queue_work(fcoe_ctlr_work_q(ctlr), work); +} + +/** + * fcoe_ctlr_device_flush_devloss() - Flush a FIP ctlr's devloss workqueue + * @ctlr: Pointer to FIP ctlr whose workqueue is to be flushed + */ +void fcoe_ctlr_device_flush_devloss(struct fcoe_ctlr_device *ctlr) +{ + if (!fcoe_ctlr_devloss_work_q(ctlr)) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to flush work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + return; + } + + flush_workqueue(fcoe_ctlr_devloss_work_q(ctlr)); +} + +/** + * fcoe_ctlr_device_queue_devloss_work() - Schedule work for a FIP ctlr's devloss workqueue + * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue + * @work: Work to queue for execution + * @delay: jiffies to delay the work queuing + * + * Return value: + * 1 on success / 0 already queued / < 0 for error + */ +int fcoe_ctlr_device_queue_devloss_work(struct fcoe_ctlr_device *ctlr, + struct delayed_work *work, + unsigned long delay) +{ + if (unlikely(!fcoe_ctlr_devloss_work_q(ctlr))) { + printk(KERN_ERR + "ERROR: FIP Ctlr '%d' attempted to queue work, " + "when no workqueue created.\n", ctlr->id); + dump_stack(); + + return -EINVAL; + } + + return queue_delayed_work(fcoe_ctlr_devloss_work_q(ctlr), work, delay); +} + +static int fcoe_fcf_device_match(struct fcoe_fcf_device *new, + struct fcoe_fcf_device *old) +{ + if (new->switch_name == old->switch_name && + new->fabric_name == old->fabric_name && + new->fc_map == old->fc_map && + compare_ether_addr(new->mac, old->mac) == 0) + return 1; + return 0; +} + +/** + * fcoe_ctlr_device_add() - Add a FIP ctlr to sysfs + * @parent: The parent device to which the fcoe_ctlr instance + * should be attached + * @f: The LLD's FCoE sysfs function template pointer + * @priv_size: Size to be allocated with the fcoe_ctlr_device for the LLD + * + * This routine allocates a FIP ctlr object with some additional memory + * for the LLD. The FIP ctlr is initialized, added to sysfs and then + * attributes are added to it. + */ +struct fcoe_ctlr_device *fcoe_ctlr_device_add(struct device *parent, + struct fcoe_sysfs_function_template *f, + int priv_size) +{ + struct fcoe_ctlr_device *ctlr; + int error = 0; + + ctlr = kzalloc(sizeof(struct fcoe_ctlr_device) + priv_size, + GFP_KERNEL); + if (!ctlr) + goto out; + + ctlr->id = atomic_inc_return(&ctlr_num) - 1; + ctlr->f = f; + INIT_LIST_HEAD(&ctlr->fcfs); + mutex_init(&ctlr->lock); + ctlr->dev.parent = parent; + ctlr->dev.bus = &fcoe_bus_type; + ctlr->dev.type = &fcoe_ctlr_device_type; + + ctlr->fcf_dev_loss_tmo = fcoe_fcf_dev_loss_tmo; + + snprintf(ctlr->work_q_name, sizeof(ctlr->work_q_name), + "ctlr_wq_%d", ctlr->id); + ctlr->work_q = create_singlethread_workqueue( + ctlr->work_q_name); + if (!ctlr->work_q) + goto out_del; + + snprintf(ctlr->devloss_work_q_name, + sizeof(ctlr->devloss_work_q_name), + "ctlr_dl_wq_%d", ctlr->id); + ctlr->devloss_work_q = create_singlethread_workqueue( + ctlr->devloss_work_q_name); + if (!ctlr->devloss_work_q) + goto out_del_q; + + dev_set_name(&ctlr->dev, "ctlr_%d", ctlr->id); + error = device_register(&ctlr->dev); + if (error) + goto out_del_q2; + + return ctlr; + +out_del_q2: + destroy_workqueue(ctlr->devloss_work_q); + ctlr->devloss_work_q = NULL; +out_del_q: + destroy_workqueue(ctlr->work_q); + ctlr->work_q = NULL; +out_del: + kfree(ctlr); +out: + return NULL; +} +EXPORT_SYMBOL_GPL(fcoe_ctlr_device_add); + +/** + * fcoe_ctlr_device_delete() - Delete a FIP ctlr and its subtree from sysfs + * @ctlr: A pointer to the ctlr to be deleted + * + * Deletes a FIP ctlr and any fcfs attached + * to it. Deleting fcfs will cause their childen + * to be deleted as well. + * + * The ctlr is detached from sysfs and it's resources + * are freed (work q), but the memory is not freed + * until its last reference is released. + * + * This routine expects no locks to be held before + * calling. + * + * TODO: Currently there are no callbacks to clean up LLD data + * for a fcoe_fcf_device. LLDs must keep this in mind as they need + * to clean up each of their LLD data for all fcoe_fcf_device before + * calling fcoe_ctlr_device_delete. + */ +void fcoe_ctlr_device_delete(struct fcoe_ctlr_device *ctlr) +{ + struct fcoe_fcf_device *fcf, *next; + /* Remove any attached fcfs */ + mutex_lock(&ctlr->lock); + list_for_each_entry_safe(fcf, next, + &ctlr->fcfs, peers) { + list_del(&fcf->peers); + fcf->state = FCOE_FCF_STATE_DELETED; + fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work); + } + mutex_unlock(&ctlr->lock); + + fcoe_ctlr_device_flush_work(ctlr); + + destroy_workqueue(ctlr->devloss_work_q); + ctlr->devloss_work_q = NULL; + destroy_workqueue(ctlr->work_q); + ctlr->work_q = NULL; + + device_unregister(&ctlr->dev); +} +EXPORT_SYMBOL_GPL(fcoe_ctlr_device_delete); + +/** + * fcoe_fcf_device_final_delete() - Final delete routine + * @work: The FIP fcf's embedded work struct + * + * It is expected that the fcf has been removed from + * the FIP ctlr's list before calling this routine. + */ +static void fcoe_fcf_device_final_delete(struct work_struct *work) +{ + struct fcoe_fcf_device *fcf = + container_of(work, struct fcoe_fcf_device, delete_work); + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); + + /* + * Cancel any outstanding timers. These should really exist + * only when rmmod'ing the LLDD and we're asking for + * immediate termination of the rports + */ + if (!cancel_delayed_work(&fcf->dev_loss_work)) + fcoe_ctlr_device_flush_devloss(ctlr); + + device_unregister(&fcf->dev); +} + +/** + * fip_timeout_deleted_fcf() - Delete a fcf when the devloss timer fires + * @work: The FIP fcf's embedded work struct + * + * Removes the fcf from the FIP ctlr's list of fcfs and + * queues the final deletion. + */ +static void fip_timeout_deleted_fcf(struct work_struct *work) +{ + struct fcoe_fcf_device *fcf = + container_of(work, struct fcoe_fcf_device, dev_loss_work.work); + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); + + mutex_lock(&ctlr->lock); + + /* + * If the fcf is deleted or reconnected before the timer + * fires the devloss queue will be flushed, but the state will + * either be CONNECTED or DELETED. If that is the case we + * cancel deleting the fcf. + */ + if (fcf->state != FCOE_FCF_STATE_DISCONNECTED) + goto out; + + dev_printk(KERN_ERR, &fcf->dev, + "FIP fcf connection time out: removing fcf\n"); + + list_del(&fcf->peers); + fcf->state = FCOE_FCF_STATE_DELETED; + fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work); + +out: + mutex_unlock(&ctlr->lock); +} + +/** + * fcoe_fcf_device_delete() - Delete a FIP fcf + * @fcf: Pointer to the fcf which is to be deleted + * + * Queues the FIP fcf on the devloss workqueue + * + * Expects the ctlr_attrs mutex to be held for fcf + * state change. + */ +void fcoe_fcf_device_delete(struct fcoe_fcf_device *fcf) +{ + struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf); + int timeout = fcf->dev_loss_tmo; + + if (fcf->state != FCOE_FCF_STATE_CONNECTED) + return; + + fcf->state = FCOE_FCF_STATE_DISCONNECTED; + + /* + * FCF will only be re-connected by the LLD calling + * fcoe_fcf_device_add, and it should be setting up + * priv then. + */ + fcf->priv = NULL; + + fcoe_ctlr_device_queue_devloss_work(ctlr, &fcf->dev_loss_work, + timeout * HZ); +} +EXPORT_SYMBOL_GPL(fcoe_fcf_device_delete); + +/** + * fcoe_fcf_device_add() - Add a FCoE sysfs fcoe_fcf_device to the system + * @ctlr: The fcoe_ctlr_device that will be the fcoe_fcf_device parent + * @new_fcf: A temporary FCF used for lookups on the current list of fcfs + * + * Expects to be called with the ctlr->lock held + */ +struct fcoe_fcf_device *fcoe_fcf_device_add(struct fcoe_ctlr_device *ctlr, + struct fcoe_fcf_device *new_fcf) +{ + struct fcoe_fcf_device *fcf; + int error = 0; + + list_for_each_entry(fcf, &ctlr->fcfs, peers) { + if (fcoe_fcf_device_match(new_fcf, fcf)) { + if (fcf->state == FCOE_FCF_STATE_CONNECTED) + return fcf; + + fcf->state = FCOE_FCF_STATE_CONNECTED; + + if (!cancel_delayed_work(&fcf->dev_loss_work)) + fcoe_ctlr_device_flush_devloss(ctlr); + + return fcf; + } + } + + fcf = kzalloc(sizeof(struct fcoe_fcf_device), GFP_ATOMIC); + if (unlikely(!fcf)) + goto out; + + INIT_WORK(&fcf->delete_work, fcoe_fcf_device_final_delete); + INIT_DELAYED_WORK(&fcf->dev_loss_work, fip_timeout_deleted_fcf); + + fcf->dev.parent = &ctlr->dev; + fcf->dev.bus = &fcoe_bus_type; + fcf->dev.type = &fcoe_fcf_device_type; + fcf->id = atomic_inc_return(&fcf_num) - 1; + fcf->state = FCOE_FCF_STATE_UNKNOWN; + + fcf->dev_loss_tmo = ctlr->fcf_dev_loss_tmo; + + dev_set_name(&fcf->dev, "fcf_%d", fcf->id); + + fcf->fabric_name = new_fcf->fabric_name; + fcf->switch_name = new_fcf->switch_name; + fcf->fc_map = new_fcf->fc_map; + fcf->vfid = new_fcf->vfid; + memcpy(fcf->mac, new_fcf->mac, ETH_ALEN); + fcf->priority = new_fcf->priority; + fcf->fka_period = new_fcf->fka_period; + fcf->selected = new_fcf->selected; + + error = device_register(&fcf->dev); + if (error) + goto out_del; + + fcf->state = FCOE_FCF_STATE_CONNECTED; + list_add_tail(&fcf->peers, &ctlr->fcfs); + + return fcf; + +out_del: + kfree(fcf); +out: + return NULL; +} +EXPORT_SYMBOL_GPL(fcoe_fcf_device_add); + +int __init fcoe_sysfs_setup(void) +{ + int error; + + atomic_set(&ctlr_num, 0); + atomic_set(&fcf_num, 0); + + error = bus_register(&fcoe_bus_type); + if (error) + return error; + + return 0; +} + +void __exit fcoe_sysfs_teardown(void) +{ + bus_unregister(&fcoe_bus_type); +} diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c index 710e149d41b..b46f43dced7 100644 --- a/drivers/scsi/fcoe/fcoe_transport.c +++ b/drivers/scsi/fcoe/fcoe_transport.c @@ -815,9 +815,17 @@ out_nodev: */ static int __init libfcoe_init(void) { - fcoe_transport_init(); + int rc = 0; - return 0; + rc = fcoe_transport_init(); + if (rc) + return rc; + + rc = fcoe_sysfs_setup(); + if (rc) + fcoe_transport_exit(); + + return rc; } module_init(libfcoe_init); @@ -826,6 +834,7 @@ module_init(libfcoe_init); */ static void __exit libfcoe_exit(void) { + fcoe_sysfs_teardown(); fcoe_transport_exit(); } module_exit(libfcoe_exit); diff --git a/drivers/scsi/qla2xxx/Kconfig b/drivers/scsi/qla2xxx/Kconfig index 6208d562890..317a7fdc3b8 100644 --- a/drivers/scsi/qla2xxx/Kconfig +++ b/drivers/scsi/qla2xxx/Kconfig @@ -25,3 +25,12 @@ config SCSI_QLA_FC Firmware images can be retrieved from: ftp://ftp.qlogic.com/outgoing/linux/firmware/ + +config TCM_QLA2XXX + tristate "TCM_QLA2XXX fabric module for Qlogic 2xxx series target mode HBAs" + depends on SCSI_QLA_FC && TARGET_CORE + select LIBFC + select BTREE + default n + ---help--- + Say Y here to enable the TCM_QLA2XXX fabric module for Qlogic 2xxx series target mode HBAs diff --git a/drivers/scsi/qla2xxx/Makefile b/drivers/scsi/qla2xxx/Makefile index 5df782f4a09..dce7d788cdc 100644 --- a/drivers/scsi/qla2xxx/Makefile +++ b/drivers/scsi/qla2xxx/Makefile @@ -1,5 +1,6 @@ qla2xxx-y := qla_os.o qla_init.o qla_mbx.o qla_iocb.o qla_isr.o qla_gs.o \ qla_dbg.o qla_sup.o qla_attr.o qla_mid.o qla_dfs.o qla_bsg.o \ - qla_nx.o + qla_nx.o qla_target.o obj-$(CONFIG_SCSI_QLA_FC) += qla2xxx.o +obj-$(CONFIG_TCM_QLA2XXX) += tcm_qla2xxx.o diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/qla_attr.c index 5926f5a87ea..5ab953029f8 100644 --- a/drivers/scsi/qla2xxx/qla_attr.c +++ b/drivers/scsi/qla2xxx/qla_attr.c @@ -5,6 +5,7 @@ * See LICENSE.qla2xxx for copyright and licensing details. */ #include "qla_def.h" +#include "qla_target.h" #include <linux/kthread.h> #include <linux/vmalloc.h> @@ -576,6 +577,7 @@ qla2x00_sysfs_write_reset(struct file *filp, struct kobject *kobj, scsi_block_requests(vha->host); set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); if (IS_QLA82XX(ha)) { + ha->flags.isp82xx_no_md_cap = 1; qla82xx_idc_lock(ha); qla82xx_set_reset_owner(vha); qla82xx_idc_unlock(ha); @@ -585,7 +587,7 @@ qla2x00_sysfs_write_reset(struct file *filp, struct kobject *kobj, scsi_unblock_requests(vha->host); break; case 0x2025d: - if (!IS_QLA81XX(ha)) + if (!IS_QLA81XX(ha) || !IS_QLA8031(ha)) return -EPERM; ql_log(ql_log_info, vha, 0x706f, @@ -1105,9 +1107,8 @@ qla2x00_total_isp_aborts_show(struct device *dev, struct device_attribute *attr, char *buf) { scsi_qla_host_t *vha = shost_priv(class_to_shost(dev)); - struct qla_hw_data *ha = vha->hw; return snprintf(buf, PAGE_SIZE, "%d\n", - ha->qla_stats.total_isp_aborts); + vha->qla_stats.total_isp_aborts); } static ssize_t @@ -1154,7 +1155,7 @@ qla2x00_phy_version_show(struct device *dev, struct device_attribute *attr, scsi_qla_host_t *vha = shost_priv(class_to_shost(dev)); struct qla_hw_data *ha = vha->hw; - if (!IS_QLA81XX(ha) && !IS_QLA83XX(ha)) + if (!IS_QLA81XX(ha) && !IS_QLA8031(ha)) return snprintf(buf, PAGE_SIZE, "\n"); return snprintf(buf, PAGE_SIZE, "%d.%02d.%02d\n", @@ -1537,7 +1538,7 @@ qla2x00_get_fc_host_stats(struct Scsi_Host *shost) dma_addr_t stats_dma; struct fc_host_statistics *pfc_host_stat; - pfc_host_stat = &ha->fc_host_stat; + pfc_host_stat = &vha->fc_host_stat; memset(pfc_host_stat, -1, sizeof(struct fc_host_statistics)); if (test_bit(UNLOADING, &vha->dpc_flags)) @@ -1580,8 +1581,8 @@ qla2x00_get_fc_host_stats(struct Scsi_Host *shost) pfc_host_stat->dumped_frames = stats->dumped_frames; pfc_host_stat->nos_count = stats->nos_rcvd; } - pfc_host_stat->fcp_input_megabytes = ha->qla_stats.input_bytes >> 20; - pfc_host_stat->fcp_output_megabytes = ha->qla_stats.output_bytes >> 20; + pfc_host_stat->fcp_input_megabytes = vha->qla_stats.input_bytes >> 20; + pfc_host_stat->fcp_output_megabytes = vha->qla_stats.output_bytes >> 20; done_free: dma_pool_free(ha->s_dma_pool, stats, stats_dma); @@ -1737,6 +1738,7 @@ qla24xx_vport_create(struct fc_vport *fc_vport, bool disable) fc_host_supported_speeds(vha->host) = fc_host_supported_speeds(base_vha->host); + qlt_vport_create(vha, ha); qla24xx_vport_disable(fc_vport, disable); if (ha->flags.cpu_affinity_enabled) { @@ -1951,12 +1953,16 @@ qla2x00_init_host_attr(scsi_qla_host_t *vha) fc_host_dev_loss_tmo(vha->host) = ha->port_down_retry_count; fc_host_node_name(vha->host) = wwn_to_u64(vha->node_name); fc_host_port_name(vha->host) = wwn_to_u64(vha->port_name); - fc_host_supported_classes(vha->host) = FC_COS_CLASS3; + fc_host_supported_classes(vha->host) = ha->tgt.enable_class_2 ? + (FC_COS_CLASS2|FC_COS_CLASS3) : FC_COS_CLASS3; fc_host_max_npiv_vports(vha->host) = ha->max_npiv_vports; fc_host_npiv_vports_inuse(vha->host) = ha->cur_vport_count; if (IS_CNA_CAPABLE(ha)) speed = FC_PORTSPEED_10GBIT; + else if (IS_QLA2031(ha)) + speed = FC_PORTSPEED_16GBIT | FC_PORTSPEED_8GBIT | + FC_PORTSPEED_4GBIT; else if (IS_QLA25XX(ha)) speed = FC_PORTSPEED_8GBIT | FC_PORTSPEED_4GBIT | FC_PORTSPEED_2GBIT | FC_PORTSPEED_1GBIT; diff --git a/drivers/scsi/qla2xxx/qla_bsg.c b/drivers/scsi/qla2xxx/qla_bsg.c index bc3cc6d9111..c68883806c5 100644 --- a/drivers/scsi/qla2xxx/qla_bsg.c +++ b/drivers/scsi/qla2xxx/qla_bsg.c @@ -297,7 +297,6 @@ qla2x00_process_els(struct fc_bsg_job *bsg_job) /* Initialize all required fields of fcport */ fcport->vha = vha; - fcport->vp_idx = vha->vp_idx; fcport->d_id.b.al_pa = bsg_job->request->rqst_data.h_els.port_id[0]; fcport->d_id.b.area = @@ -483,7 +482,6 @@ qla2x00_process_ct(struct fc_bsg_job *bsg_job) /* Initialize all required fields of fcport */ fcport->vha = vha; - fcport->vp_idx = vha->vp_idx; fcport->d_id.b.al_pa = bsg_job->request->rqst_data.h_ct.port_id[0]; fcport->d_id.b.area = bsg_job->request->rqst_data.h_ct.port_id[1]; fcport->d_id.b.domain = bsg_job->request->rqst_data.h_ct.port_id[2]; @@ -544,7 +542,7 @@ qla81xx_set_internal_loopback(scsi_qla_host_t *vha, uint16_t *config, int rval = 0; struct qla_hw_data *ha = vha->hw; - if (!IS_QLA81XX(ha) && !IS_QLA83XX(ha)) + if (!IS_QLA81XX(ha) && !IS_QLA8031(ha)) goto done_set_internal; new_config[0] = config[0] | (ENABLE_INTERNAL_LOOPBACK << 1); @@ -586,7 +584,7 @@ qla81xx_reset_internal_loopback(scsi_qla_host_t *vha, uint16_t *config, uint16_t new_config[4]; struct qla_hw_data *ha = vha->hw; - if (!IS_QLA81XX(ha) && !IS_QLA83XX(ha)) + if (!IS_QLA81XX(ha) && !IS_QLA8031(ha)) goto done_reset_internal; memset(new_config, 0 , sizeof(new_config)); @@ -710,8 +708,7 @@ qla2x00_process_loopback(struct fc_bsg_job *bsg_job) elreq.options = bsg_job->request->rqst_data.h_vendor.vendor_cmd[1]; if ((ha->current_topology == ISP_CFG_F || - (atomic_read(&vha->loop_state) == LOOP_DOWN) || - ((IS_QLA81XX(ha) || IS_QLA83XX(ha)) && + ((IS_QLA81XX(ha) || IS_QLA8031(ha)) && le32_to_cpu(*(uint32_t *)req_data) == ELS_OPCODE_BYTE && req_data_len == MAX_ELS_FRAME_PAYLOAD)) && elreq.options == EXTERNAL_LOOPBACK) { @@ -1402,6 +1399,9 @@ qla2x00_update_optrom(struct fc_bsg_job *bsg_job) if (rval) return rval; + /* Set the isp82xx_no_md_cap not to capture minidump */ + ha->flags.isp82xx_no_md_cap = 1; + sg_copy_to_buffer(bsg_job->request_payload.sg_list, bsg_job->request_payload.sg_cnt, ha->optrom_buffer, ha->optrom_region_size); diff --git a/drivers/scsi/qla2xxx/qla_dbg.c b/drivers/scsi/qla2xxx/qla_dbg.c index 62324a1d557..fdee5611f3e 100644 --- a/drivers/scsi/qla2xxx/qla_dbg.c +++ b/drivers/scsi/qla2xxx/qla_dbg.c @@ -11,27 +11,31 @@ * ---------------------------------------------------------------------- * | Level | Last Value Used | Holes | * ---------------------------------------------------------------------- - * | Module Init and Probe | 0x0120 | 0x4b,0xba,0xfa | - * | Mailbox commands | 0x113e | 0x112c-0x112e | + * | Module Init and Probe | 0x0122 | 0x4b,0xba,0xfa | + * | Mailbox commands | 0x1140 | 0x111a-0x111b | + * | | | 0x112c-0x112e | * | | | 0x113a | * | Device Discovery | 0x2086 | 0x2020-0x2022 | * | Queue Command and IO tracing | 0x3030 | 0x3006,0x3008 | * | | | 0x302d-0x302e | - * | DPC Thread | 0x401c | | - * | Async Events | 0x505d | 0x502b-0x502f | + * | DPC Thread | 0x401c | 0x4002,0x4013 | + * | Async Events | 0x505f | 0x502b-0x502f | * | | | 0x5047,0x5052 | - * | Timer Routines | 0x6011 | 0x600e-0x600f | + * | Timer Routines | 0x6011 | | * | User Space Interactions | 0x709f | 0x7018,0x702e, | * | | | 0x7039,0x7045, | * | | | 0x7073-0x7075, | * | | | 0x708c | * | Task Management | 0x803c | 0x8025-0x8026 | * | | | 0x800b,0x8039 | - * | AER/EEH | 0x900f | | + * | AER/EEH | 0x9011 | | * | Virtual Port | 0xa007 | | - * | ISP82XX Specific | 0xb054 | 0xb053 | + * | ISP82XX Specific | 0xb054 | 0xb024 | * | MultiQ | 0xc00c | | * | Misc | 0xd010 | | + * | Target Mode | 0xe06f | | + * | Target Mode Management | 0xf071 | | + * | Target Mode Task Management | 0x1000b | | * ---------------------------------------------------------------------- */ @@ -379,6 +383,54 @@ qla25xx_copy_fce(struct qla_hw_data *ha, void *ptr, uint32_t **last_chain) } static inline void * +qla2xxx_copy_atioqueues(struct qla_hw_data *ha, void *ptr, + uint32_t **last_chain) +{ + struct qla2xxx_mqueue_chain *q; + struct qla2xxx_mqueue_header *qh; + uint32_t num_queues; + int que; + struct { + int length; + void *ring; + } aq, *aqp; + + if (!ha->tgt.atio_q_length) + return ptr; + + num_queues = 1; + aqp = &aq; + aqp->length = ha->tgt.atio_q_length; + aqp->ring = ha->tgt.atio_ring; + + for (que = 0; que < num_queues; que++) { + /* aqp = ha->atio_q_map[que]; */ + q = ptr; + *last_chain = &q->type; + q->type = __constant_htonl(DUMP_CHAIN_QUEUE); + q->chain_size = htonl( + sizeof(struct qla2xxx_mqueue_chain) + + sizeof(struct qla2xxx_mqueue_header) + + (aqp->length * sizeof(request_t))); + ptr += sizeof(struct qla2xxx_mqueue_chain); + + /* Add header. */ + qh = ptr; + qh->queue = __constant_htonl(TYPE_ATIO_QUEUE); + qh->number = htonl(que); + qh->size = htonl(aqp->length * sizeof(request_t)); + ptr += sizeof(struct qla2xxx_mqueue_header); + + /* Add data. */ + memcpy(ptr, aqp->ring, aqp->length * sizeof(request_t)); + + ptr += aqp->length * sizeof(request_t); + } + + return ptr; +} + +static inline void * qla25xx_copy_mqueues(struct qla_hw_data *ha, void *ptr, uint32_t **last_chain) { struct qla2xxx_mqueue_chain *q; @@ -873,6 +925,8 @@ qla24xx_fw_dump(scsi_qla_host_t *vha, int hardware_locked) struct qla24xx_fw_dump *fw; uint32_t ext_mem_cnt; void *nxt; + void *nxt_chain; + uint32_t *last_chain = NULL; struct scsi_qla_host *base_vha = pci_get_drvdata(ha->pdev); if (IS_QLA82XX(ha)) @@ -1091,6 +1145,16 @@ qla24xx_fw_dump(scsi_qla_host_t *vha, int hardware_locked) qla24xx_copy_eft(ha, nxt); + nxt_chain = (void *)ha->fw_dump + ha->chain_offset; + nxt_chain = qla2xxx_copy_atioqueues(ha, nxt_chain, &last_chain); + if (last_chain) { + ha->fw_dump->version |= __constant_htonl(DUMP_CHAIN_VARIANT); + *last_chain |= __constant_htonl(DUMP_CHAIN_LAST); + } + + /* Adjust valid length. */ + ha->fw_dump_len = (nxt_chain - (void *)ha->fw_dump); + qla24xx_fw_dump_failed_0: qla2xxx_dump_post_process(base_vha, rval); @@ -1399,6 +1463,7 @@ qla25xx_fw_dump(scsi_qla_host_t *vha, int hardware_locked) /* Chain entries -- started with MQ. */ nxt_chain = qla25xx_copy_fce(ha, nxt_chain, &last_chain); nxt_chain = qla25xx_copy_mqueues(ha, nxt_chain, &last_chain); + nxt_chain = qla2xxx_copy_atioqueues(ha, nxt_chain, &last_chain); if (last_chain) { ha->fw_dump->version |= __constant_htonl(DUMP_CHAIN_VARIANT); *last_chain |= __constant_htonl(DUMP_CHAIN_LAST); @@ -1717,6 +1782,7 @@ qla81xx_fw_dump(scsi_qla_host_t *vha, int hardware_locked) /* Chain entries -- started with MQ. */ nxt_chain = qla25xx_copy_fce(ha, nxt_chain, &last_chain); nxt_chain = qla25xx_copy_mqueues(ha, nxt_chain, &last_chain); + nxt_chain = qla2xxx_copy_atioqueues(ha, nxt_chain, &last_chain); if (last_chain) { ha->fw_dump->version |= __constant_htonl(DUMP_CHAIN_VARIANT); *last_chain |= __constant_htonl(DUMP_CHAIN_LAST); @@ -2218,6 +2284,7 @@ copy_queue: /* Chain entries -- started with MQ. */ nxt_chain = qla25xx_copy_fce(ha, nxt_chain, &last_chain); nxt_chain = qla25xx_copy_mqueues(ha, nxt_chain, &last_chain); + nxt_chain = qla2xxx_copy_atioqueues(ha, nxt_chain, &last_chain); if (last_chain) { ha->fw_dump->version |= __constant_htonl(DUMP_CHAIN_VARIANT); *last_chain |= __constant_htonl(DUMP_CHAIN_LAST); diff --git a/drivers/scsi/qla2xxx/qla_dbg.h b/drivers/scsi/qla2xxx/qla_dbg.h index 2157bdf1569..f278df8cce0 100644 --- a/drivers/scsi/qla2xxx/qla_dbg.h +++ b/drivers/scsi/qla2xxx/qla_dbg.h @@ -244,6 +244,7 @@ struct qla2xxx_mqueue_header { uint32_t queue; #define TYPE_REQUEST_QUEUE 0x1 #define TYPE_RESPONSE_QUEUE 0x2 +#define TYPE_ATIO_QUEUE 0x3 uint32_t number; uint32_t size; }; @@ -339,3 +340,11 @@ ql_log_pci(uint32_t, struct pci_dev *pdev, int32_t, const char *fmt, ...); #define ql_dbg_misc 0x00010000 /* For dumping everything that is not * not covered by upper categories */ +#define ql_dbg_verbose 0x00008000 /* More verbosity for each level + * This is to be used with other levels where + * more verbosity is required. It might not + * be applicable to all the levels. + */ +#define ql_dbg_tgt 0x00004000 /* Target mode */ +#define ql_dbg_tgt_mgt 0x00002000 /* Target mode management */ +#define ql_dbg_tgt_tmr 0x00001000 /* Target mode task management */ diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index a2443031dbe..39007f53aec 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -186,6 +186,7 @@ #define RESPONSE_ENTRY_CNT_2100 64 /* Number of response entries.*/ #define RESPONSE_ENTRY_CNT_2300 512 /* Number of response entries.*/ #define RESPONSE_ENTRY_CNT_MQ 128 /* Number of response entries.*/ +#define ATIO_ENTRY_CNT_24XX 4096 /* Number of ATIO entries. */ struct req_que; @@ -1234,11 +1235,27 @@ typedef struct { * ISP queue - response queue entry definition. */ typedef struct { - uint8_t data[60]; + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t handle; /* System defined handle */ + uint8_t data[52]; uint32_t signature; #define RESPONSE_PROCESSED 0xDEADDEAD /* Signature */ } response_t; +/* + * ISP queue - ATIO queue entry definition. + */ +struct atio { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t data[58]; + uint32_t signature; +#define ATIO_PROCESSED 0xDEADDEAD /* Signature */ +}; + typedef union { uint16_t extended; struct { @@ -1719,11 +1736,13 @@ typedef struct fc_port { struct fc_rport *rport, *drport; u32 supported_classes; - uint16_t vp_idx; uint8_t fc4_type; uint8_t scan_state; } fc_port_t; +#define QLA_FCPORT_SCAN_NONE 0 +#define QLA_FCPORT_SCAN_FOUND 1 + /* * Fibre channel port/lun states. */ @@ -1747,6 +1766,7 @@ static const char * const port_state_str[] = { #define FCF_LOGIN_NEEDED BIT_1 #define FCF_FCP2_DEVICE BIT_2 #define FCF_ASYNC_SENT BIT_3 +#define FCF_CONF_COMP_SUPPORTED BIT_4 /* No loop ID flag. */ #define FC_NO_LOOP_ID 0x1000 @@ -2419,6 +2439,40 @@ struct qlfc_fw { uint32_t len; }; +struct qlt_hw_data { + /* Protected by hw lock */ + uint32_t enable_class_2:1; + uint32_t enable_explicit_conf:1; + uint32_t ini_mode_force_reverse:1; + uint32_t node_name_set:1; + + dma_addr_t atio_dma; /* Physical address. */ + struct atio *atio_ring; /* Base virtual address */ + struct atio *atio_ring_ptr; /* Current address. */ + uint16_t atio_ring_index; /* Current index. */ + uint16_t atio_q_length; + + void *target_lport_ptr; + struct qla_tgt_func_tmpl *tgt_ops; + struct qla_tgt *qla_tgt; + struct qla_tgt_cmd *cmds[MAX_OUTSTANDING_COMMANDS]; + uint16_t current_handle; + + struct qla_tgt_vp_map *tgt_vp_map; + struct mutex tgt_mutex; + struct mutex tgt_host_action_mutex; + + int saved_set; + uint16_t saved_exchange_count; + uint32_t saved_firmware_options_1; + uint32_t saved_firmware_options_2; + uint32_t saved_firmware_options_3; + uint8_t saved_firmware_options[2]; + uint8_t saved_add_firmware_options[2]; + + uint8_t tgt_node_name[WWN_SIZE]; +}; + /* * Qlogic host adapter specific data structure. */ @@ -2460,7 +2514,9 @@ struct qla_hw_data { uint32_t thermal_supported:1; uint32_t isp82xx_reset_hdlr_active:1; uint32_t isp82xx_reset_owner:1; - /* 28 bits */ + uint32_t isp82xx_no_md_cap:1; + uint32_t host_shutting_down:1; + /* 30 bits */ } flags; /* This spinlock is used to protect "io transactions", you must @@ -2804,7 +2860,6 @@ struct qla_hw_data { /* ISP2322: red, green, amber. */ uint16_t zio_mode; uint16_t zio_timer; - struct fc_host_statistics fc_host_stat; struct qla_msix_entry *msix_entries; @@ -2817,7 +2872,6 @@ struct qla_hw_data { int cur_vport_count; struct qla_chip_state_84xx *cs84xx; - struct qla_statistics qla_stats; struct isp_operations *isp_ops; struct workqueue_struct *wq; struct qlfc_fw fw_buf; @@ -2863,6 +2917,8 @@ struct qla_hw_data { dma_addr_t md_tmplt_hdr_dma; void *md_dump; uint32_t md_dump_size; + + struct qlt_hw_data tgt; }; /* @@ -2920,6 +2976,7 @@ typedef struct scsi_qla_host { #define FCOE_CTX_RESET_NEEDED 18 /* Initiate FCoE context reset */ #define MPI_RESET_NEEDED 19 /* Initiate MPI FW reset */ #define ISP_QUIESCE_NEEDED 20 /* Driver need some quiescence */ +#define SCR_PENDING 21 /* SCR in target mode */ uint32_t device_flags; #define SWITCH_FOUND BIT_0 @@ -2979,10 +3036,21 @@ typedef struct scsi_qla_host { struct req_que *req; int fw_heartbeat_counter; int seconds_since_last_heartbeat; + struct fc_host_statistics fc_host_stat; + struct qla_statistics qla_stats; atomic_t vref_count; } scsi_qla_host_t; +#define SET_VP_IDX 1 +#define SET_AL_PA 2 +#define RESET_VP_IDX 3 +#define RESET_AL_PA 4 +struct qla_tgt_vp_map { + uint8_t idx; + scsi_qla_host_t *vha; +}; + /* * Macros to help code, maintain, etc. */ diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h index 9f065804bd1..9eacd2df111 100644 --- a/drivers/scsi/qla2xxx/qla_gbl.h +++ b/drivers/scsi/qla2xxx/qla_gbl.h @@ -175,6 +175,7 @@ extern int qla2x00_vp_abort_isp(scsi_qla_host_t *); /* * Global Function Prototypes in qla_iocb.c source file. */ + extern uint16_t qla2x00_calc_iocbs_32(uint16_t); extern uint16_t qla2x00_calc_iocbs_64(uint16_t); extern void qla2x00_build_scsi_iocbs_32(srb_t *, cmd_entry_t *, uint16_t); @@ -188,6 +189,8 @@ extern uint16_t qla24xx_calc_iocbs(scsi_qla_host_t *, uint16_t); extern void qla24xx_build_scsi_iocbs(srb_t *, struct cmd_type_7 *, uint16_t); extern int qla24xx_dif_start_scsi(srb_t *); +extern void *qla2x00_alloc_iocbs(scsi_qla_host_t *, srb_t *); +extern int qla2x00_issue_marker(scsi_qla_host_t *, int); /* * Global Function Prototypes in qla_mbx.c source file. @@ -239,6 +242,9 @@ extern int qla2x00_init_firmware(scsi_qla_host_t *, uint16_t); extern int +qla2x00_get_node_name_list(scsi_qla_host_t *, void **, int *); + +extern int qla2x00_get_port_database(scsi_qla_host_t *, fc_port_t *, uint8_t); extern int @@ -383,6 +389,8 @@ extern int qla2x00_request_irqs(struct qla_hw_data *, struct rsp_que *); extern void qla2x00_free_irqs(scsi_qla_host_t *); extern int qla2x00_get_data_rate(scsi_qla_host_t *); +extern char *qla2x00_get_link_speed_str(struct qla_hw_data *); + /* * Global Function Prototypes in qla_sup.c source file. */ @@ -546,6 +554,7 @@ extern void qla2x00_sp_free(void *, void *); extern void qla2x00_sp_timeout(unsigned long); extern void qla2x00_bsg_job_done(void *, void *, int); extern void qla2x00_bsg_sp_free(void *, void *); +extern void qla2x00_start_iocbs(struct scsi_qla_host *, struct req_que *); /* Interrupt related */ extern irqreturn_t qla82xx_intr_handler(int, void *); diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/qla_gs.c index 3128f80441f..05260d25fe4 100644 --- a/drivers/scsi/qla2xxx/qla_gs.c +++ b/drivers/scsi/qla2xxx/qla_gs.c @@ -5,6 +5,7 @@ * See LICENSE.qla2xxx for copyright and licensing details. */ #include "qla_def.h" +#include "qla_target.h" static int qla2x00_sns_ga_nxt(scsi_qla_host_t *, fc_port_t *); static int qla2x00_sns_gid_pt(scsi_qla_host_t *, sw_info_t *); @@ -556,7 +557,8 @@ qla2x00_rff_id(scsi_qla_host_t *vha) ct_req->req.rff_id.port_id[1] = vha->d_id.b.area; ct_req->req.rff_id.port_id[2] = vha->d_id.b.al_pa; - ct_req->req.rff_id.fc4_feature = BIT_1; + qlt_rff_id(vha, ct_req); + ct_req->req.rff_id.fc4_type = 0x08; /* SCSI - FCP */ /* Execute MS IOCB */ diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index b9465643396..ca508474313 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -17,6 +17,9 @@ #include <asm/prom.h> #endif +#include <target/target_core_base.h> +#include "qla_target.h" + /* * QLogic ISP2x00 Hardware Support Function Prototypes. */ @@ -518,7 +521,10 @@ qla2x00_initialize_adapter(scsi_qla_host_t *vha) return QLA_FUNCTION_FAILED; } } - rval = qla2x00_init_rings(vha); + + if (qla_ini_mode_enabled(vha)) + rval = qla2x00_init_rings(vha); + ha->flags.chip_reset_done = 1; if (rval == QLA_SUCCESS && IS_QLA84XX(ha)) { @@ -1233,6 +1239,8 @@ qla2x00_alloc_fw_dump(scsi_qla_host_t *vha) mq_size += ha->max_rsp_queues * (rsp->length * sizeof(response_t)); } + if (ha->tgt.atio_q_length) + mq_size += ha->tgt.atio_q_length * sizeof(request_t); /* Allocate memory for Fibre Channel Event Buffer. */ if (!IS_QLA25XX(ha) && !IS_QLA81XX(ha) && !IS_QLA83XX(ha)) goto try_eft; @@ -1696,6 +1704,12 @@ qla24xx_config_rings(struct scsi_qla_host *vha) icb->response_q_address[0] = cpu_to_le32(LSD(rsp->dma)); icb->response_q_address[1] = cpu_to_le32(MSD(rsp->dma)); + /* Setup ATIO queue dma pointers for target mode */ + icb->atio_q_inpointer = __constant_cpu_to_le16(0); + icb->atio_q_length = cpu_to_le16(ha->tgt.atio_q_length); + icb->atio_q_address[0] = cpu_to_le32(LSD(ha->tgt.atio_dma)); + icb->atio_q_address[1] = cpu_to_le32(MSD(ha->tgt.atio_dma)); + if (ha->mqenable || IS_QLA83XX(ha)) { icb->qos = __constant_cpu_to_le16(QLA_DEFAULT_QUE_QOS); icb->rid = __constant_cpu_to_le16(rid); @@ -1739,6 +1753,8 @@ qla24xx_config_rings(struct scsi_qla_host *vha) WRT_REG_DWORD(®->isp24.rsp_q_in, 0); WRT_REG_DWORD(®->isp24.rsp_q_out, 0); } + qlt_24xx_config_rings(vha, reg); + /* PCI posting */ RD_REG_DWORD(&ioreg->hccr); } @@ -1794,6 +1810,11 @@ qla2x00_init_rings(scsi_qla_host_t *vha) spin_unlock(&ha->vport_slock); + ha->tgt.atio_ring_ptr = ha->tgt.atio_ring; + ha->tgt.atio_ring_index = 0; + /* Initialize ATIO queue entries */ + qlt_init_atio_q_entries(vha); + ha->isp_ops->config_rings(vha); spin_unlock_irqrestore(&ha->hardware_lock, flags); @@ -2051,6 +2072,10 @@ qla2x00_configure_hba(scsi_qla_host_t *vha) vha->d_id.b.area = area; vha->d_id.b.al_pa = al_pa; + spin_lock(&ha->vport_slock); + qlt_update_vp_map(vha, SET_AL_PA); + spin_unlock(&ha->vport_slock); + if (!vha->flags.init_done) ql_log(ql_log_info, vha, 0x2010, "Topology - %s, Host Loop address 0x%x.\n", @@ -2185,7 +2210,7 @@ qla2x00_nvram_config(scsi_qla_host_t *vha) nv->id[2] != 'P' || nv->id[3] != ' ' || nv->nvram_version < 1) { /* Reset NVRAM data. */ ql_log(ql_log_warn, vha, 0x0064, - "Inconisistent NVRAM " + "Inconsistent NVRAM " "detected: checksum=0x%x id=%c version=0x%x.\n", chksum, nv->id[0], nv->nvram_version); ql_log(ql_log_warn, vha, 0x0065, @@ -2270,7 +2295,7 @@ qla2x00_nvram_config(scsi_qla_host_t *vha) if (IS_QLA23XX(ha)) { nv->firmware_options[0] |= BIT_2; nv->firmware_options[0] &= ~BIT_3; - nv->firmware_options[0] &= ~BIT_6; + nv->special_options[0] &= ~BIT_6; nv->add_firmware_options[1] |= BIT_5 | BIT_4; if (IS_QLA2300(ha)) { @@ -2467,14 +2492,21 @@ qla2x00_rport_del(void *data) { fc_port_t *fcport = data; struct fc_rport *rport; + scsi_qla_host_t *vha = fcport->vha; unsigned long flags; spin_lock_irqsave(fcport->vha->host->host_lock, flags); rport = fcport->drport ? fcport->drport: fcport->rport; fcport->drport = NULL; spin_unlock_irqrestore(fcport->vha->host->host_lock, flags); - if (rport) + if (rport) { fc_remote_port_delete(rport); + /* + * Release the target mode FC NEXUS in qla_target.c code + * if target mod is enabled. + */ + qlt_fc_port_deleted(vha, fcport); + } } /** @@ -2495,11 +2527,11 @@ qla2x00_alloc_fcport(scsi_qla_host_t *vha, gfp_t flags) /* Setup fcport template structure. */ fcport->vha = vha; - fcport->vp_idx = vha->vp_idx; fcport->port_type = FCT_UNKNOWN; fcport->loop_id = FC_NO_LOOP_ID; qla2x00_set_fcport_state(fcport, FCS_UNCONFIGURED); fcport->supported_classes = FC_COS_UNSPECIFIED; + fcport->scan_state = QLA_FCPORT_SCAN_NONE; return fcport; } @@ -2726,7 +2758,6 @@ qla2x00_configure_local_loop(scsi_qla_host_t *vha) new_fcport->d_id.b.area = area; new_fcport->d_id.b.al_pa = al_pa; new_fcport->loop_id = loop_id; - new_fcport->vp_idx = vha->vp_idx; rval2 = qla2x00_get_port_database(vha, new_fcport, 0); if (rval2 != QLA_SUCCESS) { ql_dbg(ql_dbg_disc, vha, 0x201a, @@ -2760,10 +2791,6 @@ qla2x00_configure_local_loop(scsi_qla_host_t *vha) if (!found) { /* New device, add to fcports list. */ - if (vha->vp_idx) { - new_fcport->vha = vha; - new_fcport->vp_idx = vha->vp_idx; - } list_add_tail(&new_fcport->list, &vha->vp_fcports); /* Allocate a new replacement fcport. */ @@ -2800,8 +2827,6 @@ cleanup_allocation: static void qla2x00_iidma_fcport(scsi_qla_host_t *vha, fc_port_t *fcport) { -#define LS_UNKNOWN 2 - static char *link_speeds[] = { "1", "2", "?", "4", "8", "10" }; char *link_speed; int rval; uint16_t mb[4]; @@ -2829,11 +2854,7 @@ qla2x00_iidma_fcport(scsi_qla_host_t *vha, fc_port_t *fcport) fcport->port_name[6], fcport->port_name[7], rval, fcport->fp_speed, mb[0], mb[1]); } else { - link_speed = link_speeds[LS_UNKNOWN]; - if (fcport->fp_speed < 5) - link_speed = link_speeds[fcport->fp_speed]; - else if (fcport->fp_speed == 0x13) - link_speed = link_speeds[5]; + link_speed = qla2x00_get_link_speed_str(ha); ql_dbg(ql_dbg_disc, vha, 0x2005, "iIDMA adjusted to %s GB/s " "on %02x%02x%02x%02x%02x%02x%02x%02x.\n", link_speed, @@ -2864,6 +2885,12 @@ qla2x00_reg_remote_port(scsi_qla_host_t *vha, fc_port_t *fcport) "Unable to allocate fc remote port.\n"); return; } + /* + * Create target mode FC NEXUS in qla_target.c if target mode is + * enabled.. + */ + qlt_fc_port_added(vha, fcport); + spin_lock_irqsave(fcport->vha->host->host_lock, flags); *((fc_port_t **)rport->dd_data) = fcport; spin_unlock_irqrestore(fcport->vha->host->host_lock, flags); @@ -2921,7 +2948,7 @@ static int qla2x00_configure_fabric(scsi_qla_host_t *vha) { int rval; - fc_port_t *fcport, *fcptemp; + fc_port_t *fcport; uint16_t next_loopid; uint16_t mb[MAILBOX_REGISTER_COUNT]; uint16_t loop_id; @@ -2959,7 +2986,7 @@ qla2x00_configure_fabric(scsi_qla_host_t *vha) 0xfc, mb, BIT_1|BIT_0); if (rval != QLA_SUCCESS) { set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); - return rval; + break; } if (mb[0] != MBS_COMMAND_COMPLETE) { ql_dbg(ql_dbg_disc, vha, 0x2042, @@ -2991,21 +3018,16 @@ qla2x00_configure_fabric(scsi_qla_host_t *vha) } } -#define QLA_FCPORT_SCAN 1 -#define QLA_FCPORT_FOUND 2 - - list_for_each_entry(fcport, &vha->vp_fcports, list) { - fcport->scan_state = QLA_FCPORT_SCAN; - } - rval = qla2x00_find_all_fabric_devs(vha, &new_fcports); if (rval != QLA_SUCCESS) break; - /* - * Logout all previous fabric devices marked lost, except - * FCP2 devices. - */ + /* Add new ports to existing port list */ + list_splice_tail_init(&new_fcports, &vha->vp_fcports); + + /* Starting free loop ID. */ + next_loopid = ha->min_external_loopid; + list_for_each_entry(fcport, &vha->vp_fcports, list) { if (test_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags)) break; @@ -3013,7 +3035,8 @@ qla2x00_configure_fabric(scsi_qla_host_t *vha) if ((fcport->flags & FCF_FABRIC_DEVICE) == 0) continue; - if (fcport->scan_state == QLA_FCPORT_SCAN && + /* Logout lost/gone fabric devices (non-FCP2) */ + if (fcport->scan_state != QLA_FCPORT_SCAN_FOUND && atomic_read(&fcport->state) == FCS_ONLINE) { qla2x00_mark_device_lost(vha, fcport, ql2xplogiabsentdevice, 0); @@ -3026,78 +3049,30 @@ qla2x00_configure_fabric(scsi_qla_host_t *vha) fcport->d_id.b.domain, fcport->d_id.b.area, fcport->d_id.b.al_pa); - fcport->loop_id = FC_NO_LOOP_ID; } - } - } - - /* Starting free loop ID. */ - next_loopid = ha->min_external_loopid; - - /* - * Scan through our port list and login entries that need to be - * logged in. - */ - list_for_each_entry(fcport, &vha->vp_fcports, list) { - if (atomic_read(&vha->loop_down_timer) || - test_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags)) - break; - - if ((fcport->flags & FCF_FABRIC_DEVICE) == 0 || - (fcport->flags & FCF_LOGIN_NEEDED) == 0) continue; - - if (fcport->loop_id == FC_NO_LOOP_ID) { - fcport->loop_id = next_loopid; - rval = qla2x00_find_new_loop_id( - base_vha, fcport); - if (rval != QLA_SUCCESS) { - /* Ran out of IDs to use */ - break; - } } - /* Login and update database */ - qla2x00_fabric_dev_login(vha, fcport, &next_loopid); - } - - /* Exit if out of loop IDs. */ - if (rval != QLA_SUCCESS) { - break; - } - - /* - * Login and add the new devices to our port list. - */ - list_for_each_entry_safe(fcport, fcptemp, &new_fcports, list) { - if (atomic_read(&vha->loop_down_timer) || - test_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags)) - break; - - /* Find a new loop ID to use. */ - fcport->loop_id = next_loopid; - rval = qla2x00_find_new_loop_id(base_vha, fcport); - if (rval != QLA_SUCCESS) { - /* Ran out of IDs to use */ - break; + fcport->scan_state = QLA_FCPORT_SCAN_NONE; + + /* Login fabric devices that need a login */ + if ((fcport->flags & FCF_LOGIN_NEEDED) != 0 && + atomic_read(&vha->loop_down_timer) == 0) { + if (fcport->loop_id == FC_NO_LOOP_ID) { + fcport->loop_id = next_loopid; + rval = qla2x00_find_new_loop_id( + base_vha, fcport); + if (rval != QLA_SUCCESS) { + /* Ran out of IDs to use */ + continue; + } + } } /* Login and update database */ qla2x00_fabric_dev_login(vha, fcport, &next_loopid); - - if (vha->vp_idx) { - fcport->vha = vha; - fcport->vp_idx = vha->vp_idx; - } - list_move_tail(&fcport->list, &vha->vp_fcports); } } while (0); - /* Free all new device structures not processed. */ - list_for_each_entry_safe(fcport, fcptemp, &new_fcports, list) { - list_del(&fcport->list); - kfree(fcport); - } - if (rval) { ql_dbg(ql_dbg_disc, vha, 0x2068, "Configure fabric error exit rval=%d.\n", rval); @@ -3287,7 +3262,7 @@ qla2x00_find_all_fabric_devs(scsi_qla_host_t *vha, WWN_SIZE)) continue; - fcport->scan_state = QLA_FCPORT_FOUND; + fcport->scan_state = QLA_FCPORT_SCAN_FOUND; found++; @@ -3595,6 +3570,12 @@ qla2x00_fabric_login(scsi_qla_host_t *vha, fc_port_t *fcport, if (mb[10] & BIT_1) fcport->supported_classes |= FC_COS_CLASS3; + if (IS_FWI2_CAPABLE(ha)) { + if (mb[10] & BIT_7) + fcport->flags |= + FCF_CONF_COMP_SUPPORTED; + } + rval = QLA_SUCCESS; break; } else if (mb[0] == MBS_LOOP_ID_USED) { @@ -3841,7 +3822,7 @@ qla2x00_abort_isp_cleanup(scsi_qla_host_t *vha) vha->flags.online = 0; ha->flags.chip_reset_done = 0; clear_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); - ha->qla_stats.total_isp_aborts++; + vha->qla_stats.total_isp_aborts++; ql_log(ql_log_info, vha, 0x00af, "Performing ISP error recovery - ha=%p.\n", ha); @@ -4066,6 +4047,7 @@ qla2x00_restart_isp(scsi_qla_host_t *vha) struct qla_hw_data *ha = vha->hw; struct req_que *req = ha->req_q_map[0]; struct rsp_que *rsp = ha->rsp_q_map[0]; + unsigned long flags; /* If firmware needs to be loaded */ if (qla2x00_isp_firmware(vha)) { @@ -4090,6 +4072,16 @@ qla2x00_restart_isp(scsi_qla_host_t *vha) qla2x00_marker(vha, req, rsp, 0, 0, MK_SYNC_ALL); vha->flags.online = 1; + + /* + * Process any ATIO queue entries that came in + * while we weren't online. + */ + spin_lock_irqsave(&ha->hardware_lock, flags); + if (qla_tgt_mode_enabled(vha)) + qlt_24xx_process_atio_queue(vha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + /* Wait at most MAX_TARGET RSCNs for a stable link. */ wait_time = 256; do { @@ -4279,7 +4271,7 @@ qla24xx_nvram_config(scsi_qla_host_t *vha) nv->nvram_version < __constant_cpu_to_le16(ICB_VERSION)) { /* Reset NVRAM data. */ ql_log(ql_log_warn, vha, 0x006b, - "Inconisistent NVRAM detected: checksum=0x%x id=%c " + "Inconsistent NVRAM detected: checksum=0x%x id=%c " "version=0x%x.\n", chksum, nv->id[0], nv->nvram_version); ql_log(ql_log_warn, vha, 0x006c, "Falling back to functioning (yet invalid -- WWPN) " @@ -4330,6 +4322,15 @@ qla24xx_nvram_config(scsi_qla_host_t *vha) rval = 1; } + if (!qla_ini_mode_enabled(vha)) { + /* Don't enable full login after initial LIP */ + nv->firmware_options_1 &= __constant_cpu_to_le32(~BIT_13); + /* Don't enable LIP full login for initiator */ + nv->host_p &= __constant_cpu_to_le32(~BIT_10); + } + + qlt_24xx_config_nvram_stage1(vha, nv); + /* Reset Initialization control block */ memset(icb, 0, ha->init_cb_size); @@ -4357,8 +4358,10 @@ qla24xx_nvram_config(scsi_qla_host_t *vha) qla2x00_set_model_info(vha, nv->model_name, sizeof(nv->model_name), "QLA2462"); - /* Use alternate WWN? */ + qlt_24xx_config_nvram_stage2(vha, icb); + if (nv->host_p & __constant_cpu_to_le32(BIT_15)) { + /* Use alternate WWN? */ memcpy(icb->node_name, nv->alternate_node_name, WWN_SIZE); memcpy(icb->port_name, nv->alternate_port_name, WWN_SIZE); } @@ -5029,7 +5032,7 @@ qla81xx_nvram_config(scsi_qla_host_t *vha) nv->nvram_version < __constant_cpu_to_le16(ICB_VERSION)) { /* Reset NVRAM data. */ ql_log(ql_log_info, vha, 0x0073, - "Inconisistent NVRAM detected: checksum=0x%x id=%c " + "Inconsistent NVRAM detected: checksum=0x%x id=%c " "version=0x%x.\n", chksum, nv->id[0], le16_to_cpu(nv->nvram_version)); ql_log(ql_log_info, vha, 0x0074, diff --git a/drivers/scsi/qla2xxx/qla_iocb.c b/drivers/scsi/qla2xxx/qla_iocb.c index eac95092449..70dbf53d9e0 100644 --- a/drivers/scsi/qla2xxx/qla_iocb.c +++ b/drivers/scsi/qla2xxx/qla_iocb.c @@ -5,6 +5,7 @@ * See LICENSE.qla2xxx for copyright and licensing details. */ #include "qla_def.h" +#include "qla_target.h" #include <linux/blkdev.h> #include <linux/delay.h> @@ -23,18 +24,17 @@ qla2x00_get_cmd_direction(srb_t *sp) { uint16_t cflags; struct scsi_cmnd *cmd = GET_CMD_SP(sp); + struct scsi_qla_host *vha = sp->fcport->vha; cflags = 0; /* Set transfer direction */ if (cmd->sc_data_direction == DMA_TO_DEVICE) { cflags = CF_WRITE; - sp->fcport->vha->hw->qla_stats.output_bytes += - scsi_bufflen(cmd); + vha->qla_stats.output_bytes += scsi_bufflen(cmd); } else if (cmd->sc_data_direction == DMA_FROM_DEVICE) { cflags = CF_READ; - sp->fcport->vha->hw->qla_stats.input_bytes += - scsi_bufflen(cmd); + vha->qla_stats.input_bytes += scsi_bufflen(cmd); } return (cflags); } @@ -385,9 +385,10 @@ qla2x00_start_scsi(srb_t *sp) else req->cnt = req->length - (req->ring_index - cnt); + /* If still no head room then bail out */ + if (req->cnt < (req_cnt + 2)) + goto queuing_error; } - if (req->cnt < (req_cnt + 2)) - goto queuing_error; /* Build command packet */ req->current_outstanding_cmd = handle; @@ -470,7 +471,7 @@ queuing_error: /** * qla2x00_start_iocbs() - Execute the IOCB command */ -static void +void qla2x00_start_iocbs(struct scsi_qla_host *vha, struct req_que *req) { struct qla_hw_data *ha = vha->hw; @@ -571,6 +572,29 @@ qla2x00_marker(struct scsi_qla_host *vha, struct req_que *req, return (ret); } +/* + * qla2x00_issue_marker + * + * Issue marker + * Caller CAN have hardware lock held as specified by ha_locked parameter. + * Might release it, then reaquire. + */ +int qla2x00_issue_marker(scsi_qla_host_t *vha, int ha_locked) +{ + if (ha_locked) { + if (__qla2x00_marker(vha, vha->req, vha->req->rsp, 0, 0, + MK_SYNC_ALL) != QLA_SUCCESS) + return QLA_FUNCTION_FAILED; + } else { + if (qla2x00_marker(vha, vha->req, vha->req->rsp, 0, 0, + MK_SYNC_ALL) != QLA_SUCCESS) + return QLA_FUNCTION_FAILED; + } + vha->marker_needed = 0; + + return QLA_SUCCESS; +} + /** * qla24xx_calc_iocbs() - Determine number of Command Type 3 and * Continuation Type 1 IOCBs to allocate. @@ -629,11 +653,11 @@ qla24xx_build_scsi_type_6_iocbs(srb_t *sp, struct cmd_type_6 *cmd_pkt, if (cmd->sc_data_direction == DMA_TO_DEVICE) { cmd_pkt->control_flags = __constant_cpu_to_le16(CF_WRITE_DATA); - ha->qla_stats.output_bytes += scsi_bufflen(cmd); + vha->qla_stats.output_bytes += scsi_bufflen(cmd); } else if (cmd->sc_data_direction == DMA_FROM_DEVICE) { cmd_pkt->control_flags = __constant_cpu_to_le16(CF_READ_DATA); - ha->qla_stats.input_bytes += scsi_bufflen(cmd); + vha->qla_stats.input_bytes += scsi_bufflen(cmd); } cur_seg = scsi_sglist(cmd); @@ -745,13 +769,11 @@ qla24xx_build_scsi_iocbs(srb_t *sp, struct cmd_type_7 *cmd_pkt, if (cmd->sc_data_direction == DMA_TO_DEVICE) { cmd_pkt->task_mgmt_flags = __constant_cpu_to_le16(TMF_WRITE_DATA); - sp->fcport->vha->hw->qla_stats.output_bytes += - scsi_bufflen(cmd); + vha->qla_stats.output_bytes += scsi_bufflen(cmd); } else if (cmd->sc_data_direction == DMA_FROM_DEVICE) { cmd_pkt->task_mgmt_flags = __constant_cpu_to_le16(TMF_READ_DATA); - sp->fcport->vha->hw->qla_stats.input_bytes += - scsi_bufflen(cmd); + vha->qla_stats.input_bytes += scsi_bufflen(cmd); } /* One DSD is available in the Command Type 3 IOCB */ @@ -1245,7 +1267,7 @@ qla24xx_build_scsi_crc_2_iocbs(srb_t *sp, struct cmd_type_crc_2 *cmd_pkt, return QLA_SUCCESS; } - cmd_pkt->vp_index = sp->fcport->vp_idx; + cmd_pkt->vp_index = sp->fcport->vha->vp_idx; /* Set transfer direction */ if (cmd->sc_data_direction == DMA_TO_DEVICE) { @@ -1502,9 +1524,9 @@ qla24xx_start_scsi(srb_t *sp) else req->cnt = req->length - (req->ring_index - cnt); + if (req->cnt < (req_cnt + 2)) + goto queuing_error; } - if (req->cnt < (req_cnt + 2)) - goto queuing_error; /* Build command packet. */ req->current_outstanding_cmd = handle; @@ -1527,7 +1549,7 @@ qla24xx_start_scsi(srb_t *sp) cmd_pkt->port_id[0] = sp->fcport->d_id.b.al_pa; cmd_pkt->port_id[1] = sp->fcport->d_id.b.area; cmd_pkt->port_id[2] = sp->fcport->d_id.b.domain; - cmd_pkt->vp_index = sp->fcport->vp_idx; + cmd_pkt->vp_index = sp->fcport->vha->vp_idx; int_to_scsilun(cmd->device->lun, &cmd_pkt->lun); host_to_fcp_swap((uint8_t *)&cmd_pkt->lun, sizeof(cmd_pkt->lun)); @@ -1717,11 +1739,10 @@ qla24xx_dif_start_scsi(srb_t *sp) else req->cnt = req->length - (req->ring_index - cnt); + if (req->cnt < (req_cnt + 2)) + goto queuing_error; } - if (req->cnt < (req_cnt + 2)) - goto queuing_error; - status |= QDSS_GOT_Q_SPACE; /* Build header part of command packet (excluding the OPCODE). */ @@ -1898,7 +1919,7 @@ qla24xx_login_iocb(srb_t *sp, struct logio_entry_24xx *logio) logio->port_id[0] = sp->fcport->d_id.b.al_pa; logio->port_id[1] = sp->fcport->d_id.b.area; logio->port_id[2] = sp->fcport->d_id.b.domain; - logio->vp_index = sp->fcport->vp_idx; + logio->vp_index = sp->fcport->vha->vp_idx; } static void @@ -1922,7 +1943,7 @@ qla2x00_login_iocb(srb_t *sp, struct mbx_entry *mbx) mbx->mb2 = cpu_to_le16(sp->fcport->d_id.b.domain); mbx->mb3 = cpu_to_le16(sp->fcport->d_id.b.area << 8 | sp->fcport->d_id.b.al_pa); - mbx->mb9 = cpu_to_le16(sp->fcport->vp_idx); + mbx->mb9 = cpu_to_le16(sp->fcport->vha->vp_idx); } static void @@ -1935,7 +1956,7 @@ qla24xx_logout_iocb(srb_t *sp, struct logio_entry_24xx *logio) logio->port_id[0] = sp->fcport->d_id.b.al_pa; logio->port_id[1] = sp->fcport->d_id.b.area; logio->port_id[2] = sp->fcport->d_id.b.domain; - logio->vp_index = sp->fcport->vp_idx; + logio->vp_index = sp->fcport->vha->vp_idx; } static void @@ -1952,7 +1973,7 @@ qla2x00_logout_iocb(srb_t *sp, struct mbx_entry *mbx) mbx->mb2 = cpu_to_le16(sp->fcport->d_id.b.domain); mbx->mb3 = cpu_to_le16(sp->fcport->d_id.b.area << 8 | sp->fcport->d_id.b.al_pa); - mbx->mb9 = cpu_to_le16(sp->fcport->vp_idx); + mbx->mb9 = cpu_to_le16(sp->fcport->vha->vp_idx); /* Implicit: mbx->mbx10 = 0. */ } @@ -1962,7 +1983,7 @@ qla24xx_adisc_iocb(srb_t *sp, struct logio_entry_24xx *logio) logio->entry_type = LOGINOUT_PORT_IOCB_TYPE; logio->control_flags = cpu_to_le16(LCF_COMMAND_ADISC); logio->nport_handle = cpu_to_le16(sp->fcport->loop_id); - logio->vp_index = sp->fcport->vp_idx; + logio->vp_index = sp->fcport->vha->vp_idx; } static void @@ -1983,7 +2004,7 @@ qla2x00_adisc_iocb(srb_t *sp, struct mbx_entry *mbx) mbx->mb3 = cpu_to_le16(LSW(ha->async_pd_dma)); mbx->mb6 = cpu_to_le16(MSW(MSD(ha->async_pd_dma))); mbx->mb7 = cpu_to_le16(LSW(MSD(ha->async_pd_dma))); - mbx->mb9 = cpu_to_le16(sp->fcport->vp_idx); + mbx->mb9 = cpu_to_le16(sp->fcport->vha->vp_idx); } static void @@ -2009,7 +2030,7 @@ qla24xx_tm_iocb(srb_t *sp, struct tsk_mgmt_entry *tsk) tsk->port_id[0] = fcport->d_id.b.al_pa; tsk->port_id[1] = fcport->d_id.b.area; tsk->port_id[2] = fcport->d_id.b.domain; - tsk->vp_index = fcport->vp_idx; + tsk->vp_index = fcport->vha->vp_idx; if (flags == TCF_LUN_RESET) { int_to_scsilun(lun, &tsk->lun); @@ -2030,7 +2051,7 @@ qla24xx_els_iocb(srb_t *sp, struct els_entry_24xx *els_iocb) els_iocb->handle = sp->handle; els_iocb->nport_handle = cpu_to_le16(sp->fcport->loop_id); els_iocb->tx_dsd_count = __constant_cpu_to_le16(bsg_job->request_payload.sg_cnt); - els_iocb->vp_index = sp->fcport->vp_idx; + els_iocb->vp_index = sp->fcport->vha->vp_idx; els_iocb->sof_type = EST_SOFI3; els_iocb->rx_dsd_count = __constant_cpu_to_le16(bsg_job->reply_payload.sg_cnt); @@ -2160,7 +2181,7 @@ qla24xx_ct_iocb(srb_t *sp, struct ct_entry_24xx *ct_iocb) ct_iocb->handle = sp->handle; ct_iocb->nport_handle = cpu_to_le16(sp->fcport->loop_id); - ct_iocb->vp_index = sp->fcport->vp_idx; + ct_iocb->vp_index = sp->fcport->vha->vp_idx; ct_iocb->comp_status = __constant_cpu_to_le16(0); ct_iocb->cmd_dsd_count = @@ -2343,11 +2364,10 @@ sufficient_dsds: else req->cnt = req->length - (req->ring_index - cnt); + if (req->cnt < (req_cnt + 2)) + goto queuing_error; } - if (req->cnt < (req_cnt + 2)) - goto queuing_error; - ctx = sp->u.scmd.ctx = mempool_alloc(ha->ctx_mempool, GFP_ATOMIC); if (!ctx) { @@ -2362,7 +2382,7 @@ sufficient_dsds: if (!ctx->fcp_cmnd) { ql_log(ql_log_fatal, vha, 0x3011, "Failed to allocate fcp_cmnd for cmd=%p.\n", cmd); - goto queuing_error_fcp_cmnd; + goto queuing_error; } /* Initialize the DSD list and dma handle */ @@ -2400,7 +2420,7 @@ sufficient_dsds: cmd_pkt->port_id[0] = sp->fcport->d_id.b.al_pa; cmd_pkt->port_id[1] = sp->fcport->d_id.b.area; cmd_pkt->port_id[2] = sp->fcport->d_id.b.domain; - cmd_pkt->vp_index = sp->fcport->vp_idx; + cmd_pkt->vp_index = sp->fcport->vha->vp_idx; /* Build IOCB segments */ if (qla24xx_build_scsi_type_6_iocbs(sp, cmd_pkt, tot_dsds)) @@ -2489,7 +2509,7 @@ sufficient_dsds: cmd_pkt->port_id[0] = sp->fcport->d_id.b.al_pa; cmd_pkt->port_id[1] = sp->fcport->d_id.b.area; cmd_pkt->port_id[2] = sp->fcport->d_id.b.domain; - cmd_pkt->vp_index = sp->fcport->vp_idx; + cmd_pkt->vp_index = sp->fcport->vha->vp_idx; int_to_scsilun(cmd->device->lun, &cmd_pkt->lun); host_to_fcp_swap((uint8_t *)&cmd_pkt->lun, diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index ce42288049b..6f67a9d4998 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -5,6 +5,7 @@ * See LICENSE.qla2xxx for copyright and licensing details. */ #include "qla_def.h" +#include "qla_target.h" #include <linux/delay.h> #include <linux/slab.h> @@ -309,6 +310,28 @@ qla81xx_idc_event(scsi_qla_host_t *vha, uint16_t aen, uint16_t descr) "IDC failed to post ACK.\n"); } +#define LS_UNKNOWN 2 +char * +qla2x00_get_link_speed_str(struct qla_hw_data *ha) +{ + static char *link_speeds[] = {"1", "2", "?", "4", "8", "16", "10"}; + char *link_speed; + int fw_speed = ha->link_data_rate; + + if (IS_QLA2100(ha) || IS_QLA2200(ha)) + link_speed = link_speeds[0]; + else if (fw_speed == 0x13) + link_speed = link_speeds[6]; + else { + link_speed = link_speeds[LS_UNKNOWN]; + if (fw_speed < 6) + link_speed = + link_speeds[fw_speed]; + } + + return link_speed; +} + /** * qla2x00_async_event() - Process aynchronous events. * @ha: SCSI driver HA context @@ -317,9 +340,6 @@ qla81xx_idc_event(scsi_qla_host_t *vha, uint16_t aen, uint16_t descr) void qla2x00_async_event(scsi_qla_host_t *vha, struct rsp_que *rsp, uint16_t *mb) { -#define LS_UNKNOWN 2 - static char *link_speeds[] = { "1", "2", "?", "4", "8", "16", "10" }; - char *link_speed; uint16_t handle_cnt; uint16_t cnt, mbx; uint32_t handles[5]; @@ -454,8 +474,8 @@ skip_rio: case MBA_WAKEUP_THRES: /* Request Queue Wake-up */ ql_dbg(ql_dbg_async, vha, 0x5008, "Asynchronous WAKEUP_THRES.\n"); - break; + break; case MBA_LIP_OCCURRED: /* Loop Initialization Procedure */ ql_dbg(ql_dbg_async, vha, 0x5009, "LIP occurred (%x).\n", mb[1]); @@ -479,20 +499,14 @@ skip_rio: break; case MBA_LOOP_UP: /* Loop Up Event */ - if (IS_QLA2100(ha) || IS_QLA2200(ha)) { - link_speed = link_speeds[0]; + if (IS_QLA2100(ha) || IS_QLA2200(ha)) ha->link_data_rate = PORT_SPEED_1GB; - } else { - link_speed = link_speeds[LS_UNKNOWN]; - if (mb[1] < 6) - link_speed = link_speeds[mb[1]]; - else if (mb[1] == 0x13) - link_speed = link_speeds[6]; + else ha->link_data_rate = mb[1]; - } ql_dbg(ql_dbg_async, vha, 0x500a, - "LOOP UP detected (%s Gbps).\n", link_speed); + "LOOP UP detected (%s Gbps).\n", + qla2x00_get_link_speed_str(ha)); vha->flags.management_server_logged_in = 0; qla2x00_post_aen_work(vha, FCH_EVT_LINKUP, ha->link_data_rate); @@ -638,6 +652,8 @@ skip_rio: ql_dbg(ql_dbg_async, vha, 0x5010, "Port unavailable %04x %04x %04x.\n", mb[1], mb[2], mb[3]); + ql_log(ql_log_warn, vha, 0x505e, + "Link is offline.\n"); if (atomic_read(&vha->loop_state) != LOOP_DOWN) { atomic_set(&vha->loop_state, LOOP_DOWN); @@ -670,12 +686,17 @@ skip_rio: ql_dbg(ql_dbg_async, vha, 0x5011, "Asynchronous PORT UPDATE ignored %04x/%04x/%04x.\n", mb[1], mb[2], mb[3]); + + qlt_async_event(mb[0], vha, mb); break; } ql_dbg(ql_dbg_async, vha, 0x5012, "Port database changed %04x %04x %04x.\n", mb[1], mb[2], mb[3]); + ql_log(ql_log_warn, vha, 0x505f, + "Link is operational (%s Gbps).\n", + qla2x00_get_link_speed_str(ha)); /* * Mark all devices as missing so we will login again. @@ -684,8 +705,13 @@ skip_rio: qla2x00_mark_all_devices_lost(vha, 1); + if (vha->vp_idx == 0 && !qla_ini_mode_enabled(vha)) + set_bit(SCR_PENDING, &vha->dpc_flags); + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); set_bit(LOCAL_LOOP_UPDATE, &vha->dpc_flags); + + qlt_async_event(mb[0], vha, mb); break; case MBA_RSCN_UPDATE: /* State Change Registration */ @@ -807,6 +833,8 @@ skip_rio: mb[0], mb[1], mb[2], mb[3]); } + qlt_async_event(mb[0], vha, mb); + if (!vha->vp_idx && ha->num_vhosts) qla2x00_alert_all_vps(rsp, mb); } @@ -1172,6 +1200,9 @@ qla24xx_logio_entry(scsi_qla_host_t *vha, struct req_que *req, } else if (iop[0] & BIT_5) fcport->port_type = FCT_INITIATOR; + if (iop[0] & BIT_7) + fcport->flags |= FCF_CONF_COMP_SUPPORTED; + if (logio->io_parameter[7] || logio->io_parameter[8]) fcport->supported_classes |= FC_COS_CLASS2; if (logio->io_parameter[9] || logio->io_parameter[10]) @@ -1986,6 +2017,9 @@ void qla24xx_process_response_queue(struct scsi_qla_host *vha, if (pkt->entry_status != 0) { qla2x00_error_entry(vha, rsp, (sts_entry_t *) pkt); + + (void)qlt_24xx_process_response_error(vha, pkt); + ((response_t *)pkt)->signature = RESPONSE_PROCESSED; wmb(); continue; @@ -2016,6 +2050,14 @@ void qla24xx_process_response_queue(struct scsi_qla_host *vha, case ELS_IOCB_TYPE: qla24xx_els_ct_entry(vha, rsp->req, pkt, ELS_IOCB_TYPE); break; + case ABTS_RECV_24XX: + /* ensure that the ATIO queue is empty */ + qlt_24xx_process_atio_queue(vha); + case ABTS_RESP_24XX: + case CTIO_TYPE7: + case NOTIFY_ACK_TYPE: + qlt_response_pkt_all_vps(vha, (response_t *)pkt); + break; case MARKER_TYPE: /* Do nothing in this case, this check is to prevent it * from falling into default case @@ -2168,6 +2210,13 @@ qla24xx_intr_handler(int irq, void *dev_id) case 0x14: qla24xx_process_response_queue(vha, rsp); break; + case 0x1C: /* ATIO queue updated */ + qlt_24xx_process_atio_queue(vha); + break; + case 0x1D: /* ATIO and response queues updated */ + qlt_24xx_process_atio_queue(vha); + qla24xx_process_response_queue(vha, rsp); + break; default: ql_dbg(ql_dbg_async, vha, 0x504f, "Unrecognized interrupt type (%d).\n", stat * 0xff); @@ -2312,6 +2361,13 @@ qla24xx_msix_default(int irq, void *dev_id) case 0x14: qla24xx_process_response_queue(vha, rsp); break; + case 0x1C: /* ATIO queue updated */ + qlt_24xx_process_atio_queue(vha); + break; + case 0x1D: /* ATIO and response queues updated */ + qlt_24xx_process_atio_queue(vha); + qla24xx_process_response_queue(vha, rsp); + break; default: ql_dbg(ql_dbg_async, vha, 0x5051, "Unrecognized interrupt type (%d).\n", stat & 0xff); @@ -2564,7 +2620,15 @@ void qla2x00_free_irqs(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; - struct rsp_que *rsp = ha->rsp_q_map[0]; + struct rsp_que *rsp; + + /* + * We need to check that ha->rsp_q_map is valid in case we are called + * from a probe failure context. + */ + if (!ha->rsp_q_map || !ha->rsp_q_map[0]) + return; + rsp = ha->rsp_q_map[0]; if (ha->flags.msix_enabled) qla24xx_disable_msix(ha); diff --git a/drivers/scsi/qla2xxx/qla_mbx.c b/drivers/scsi/qla2xxx/qla_mbx.c index b4a23394a7b..d5ce92c0a8f 100644 --- a/drivers/scsi/qla2xxx/qla_mbx.c +++ b/drivers/scsi/qla2xxx/qla_mbx.c @@ -5,6 +5,7 @@ * See LICENSE.qla2xxx for copyright and licensing details. */ #include "qla_def.h" +#include "qla_target.h" #include <linux/delay.h> #include <linux/gfp.h> @@ -270,11 +271,8 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) ictrl = RD_REG_WORD(®->isp.ictrl); } ql_dbg(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1119, - "MBX Command timeout for cmd %x.\n", command); - ql_dbg(ql_dbg_mbx + ql_dbg_buffer, vha, 0x111a, - "iocontrol=%x jiffies=%lx.\n", ictrl, jiffies); - ql_dbg(ql_dbg_mbx + ql_dbg_buffer, vha, 0x111b, - "mb[0] = 0x%x.\n", mb0); + "MBX Command timeout for cmd %x, iocontrol=%x jiffies=%lx " + "mb[0]=0x%x\n", command, ictrl, jiffies, mb0); ql_dump_regs(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1019); /* @@ -320,7 +318,7 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) CRB_NIU_XG_PAUSE_CTL_P1); } ql_log(ql_log_info, base_vha, 0x101c, - "Mailbox cmd timeout occured, cmd=0x%x, " + "Mailbox cmd timeout occurred, cmd=0x%x, " "mb[0]=0x%x, eeh_busy=0x%x. Scheduling ISP " "abort.\n", command, mcp->mb[0], ha->flags.eeh_busy); @@ -345,7 +343,7 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) CRB_NIU_XG_PAUSE_CTL_P1); } ql_log(ql_log_info, base_vha, 0x101e, - "Mailbox cmd timeout occured, cmd=0x%x, " + "Mailbox cmd timeout occurred, cmd=0x%x, " "mb[0]=0x%x. Scheduling ISP abort ", command, mcp->mb[0]); set_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags); @@ -390,7 +388,8 @@ qla2x00_load_ram(scsi_qla_host_t *vha, dma_addr_t req_dma, uint32_t risc_addr, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1022, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1022, + "Entered %s.\n", __func__); if (MSW(risc_addr) || IS_FWI2_CAPABLE(ha)) { mcp->mb[0] = MBC_LOAD_RISC_RAM_EXTENDED; @@ -424,7 +423,8 @@ qla2x00_load_ram(scsi_qla_host_t *vha, dma_addr_t req_dma, uint32_t risc_addr, ql_dbg(ql_dbg_mbx, vha, 0x1023, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1024, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1024, + "Done %s.\n", __func__); } return rval; @@ -454,7 +454,8 @@ qla2x00_execute_fw(scsi_qla_host_t *vha, uint32_t risc_addr) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1025, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1025, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_EXECUTE_FIRMWARE; mcp->out_mb = MBX_0; @@ -489,10 +490,11 @@ qla2x00_execute_fw(scsi_qla_host_t *vha, uint32_t risc_addr) "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { if (IS_FWI2_CAPABLE(ha)) { - ql_dbg(ql_dbg_mbx, vha, 0x1027, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1027, "Done exchanges=%x.\n", mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1028, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1028, + "Done %s.\n", __func__); } } @@ -523,7 +525,8 @@ qla2x00_get_fw_version(scsi_qla_host_t *vha) mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x1029, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1029, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_FIRMWARE_VERSION; mcp->out_mb = MBX_0; @@ -561,11 +564,11 @@ qla2x00_get_fw_version(scsi_qla_host_t *vha) ha->fw_attributes_h = mcp->mb[15]; ha->fw_attributes_ext[0] = mcp->mb[16]; ha->fw_attributes_ext[1] = mcp->mb[17]; - ql_dbg(ql_dbg_mbx, vha, 0x1139, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1139, "%s: FW_attributes Upper: 0x%x, Lower: 0x%x.\n", __func__, mcp->mb[15], mcp->mb[6]); } else - ql_dbg(ql_dbg_mbx, vha, 0x112f, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x112f, "%s: FwAttributes [Upper] invalid, MB6:%04x\n", __func__, mcp->mb[6]); } @@ -576,7 +579,8 @@ failed: ql_dbg(ql_dbg_mbx, vha, 0x102a, "Failed=%x.\n", rval); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x102b, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x102b, + "Done %s.\n", __func__); } return rval; } @@ -602,7 +606,8 @@ qla2x00_get_fw_options(scsi_qla_host_t *vha, uint16_t *fwopts) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x102c, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x102c, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_FIRMWARE_OPTION; mcp->out_mb = MBX_0; @@ -620,7 +625,8 @@ qla2x00_get_fw_options(scsi_qla_host_t *vha, uint16_t *fwopts) fwopts[2] = mcp->mb[2]; fwopts[3] = mcp->mb[3]; - ql_dbg(ql_dbg_mbx, vha, 0x102e, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x102e, + "Done %s.\n", __func__); } return rval; @@ -648,7 +654,8 @@ qla2x00_set_fw_options(scsi_qla_host_t *vha, uint16_t *fwopts) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x102f, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x102f, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_SET_FIRMWARE_OPTION; mcp->mb[1] = fwopts[1]; @@ -676,7 +683,8 @@ qla2x00_set_fw_options(scsi_qla_host_t *vha, uint16_t *fwopts) "Failed=%x (%x/%x).\n", rval, mcp->mb[0], mcp->mb[1]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1031, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1031, + "Done %s.\n", __func__); } return rval; @@ -704,7 +712,8 @@ qla2x00_mbx_reg_test(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1032, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1032, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_MAILBOX_REGISTER_TEST; mcp->mb[1] = 0xAAAA; @@ -734,7 +743,8 @@ qla2x00_mbx_reg_test(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx, vha, 0x1033, "Failed=%x.\n", rval); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1034, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1034, + "Done %s.\n", __func__); } return rval; @@ -762,7 +772,8 @@ qla2x00_verify_checksum(scsi_qla_host_t *vha, uint32_t risc_addr) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1035, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1035, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_VERIFY_CHECKSUM; mcp->out_mb = MBX_0; @@ -787,7 +798,8 @@ qla2x00_verify_checksum(scsi_qla_host_t *vha, uint32_t risc_addr) "Failed=%x chm sum=%x.\n", rval, IS_FWI2_CAPABLE(vha->hw) ? (mcp->mb[2] << 16) | mcp->mb[1] : mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1037, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1037, + "Done %s.\n", __func__); } return rval; @@ -819,7 +831,8 @@ qla2x00_issue_iocb_timeout(scsi_qla_host_t *vha, void *buffer, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1038, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1038, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_IOCB_COMMAND_A64; mcp->mb[1] = 0; @@ -842,7 +855,8 @@ qla2x00_issue_iocb_timeout(scsi_qla_host_t *vha, void *buffer, /* Mask reserved bits. */ sts_entry->entry_status &= IS_FWI2_CAPABLE(vha->hw) ? RF_MASK_24XX : RF_MASK; - ql_dbg(ql_dbg_mbx, vha, 0x103a, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x103a, + "Done %s.\n", __func__); } return rval; @@ -884,7 +898,8 @@ qla2x00_abort_command(srb_t *sp) struct req_que *req = vha->req; struct scsi_cmnd *cmd = GET_CMD_SP(sp); - ql_dbg(ql_dbg_mbx, vha, 0x103b, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x103b, + "Entered %s.\n", __func__); spin_lock_irqsave(&ha->hardware_lock, flags); for (handle = 1; handle < MAX_OUTSTANDING_COMMANDS; handle++) { @@ -915,7 +930,8 @@ qla2x00_abort_command(srb_t *sp) if (rval != QLA_SUCCESS) { ql_dbg(ql_dbg_mbx, vha, 0x103c, "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x103d, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x103d, + "Done %s.\n", __func__); } return rval; @@ -934,7 +950,8 @@ qla2x00_abort_target(struct fc_port *fcport, unsigned int l, int tag) l = l; vha = fcport->vha; - ql_dbg(ql_dbg_mbx, vha, 0x103e, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x103e, + "Entered %s.\n", __func__); req = vha->hw->req_q_map[0]; rsp = req->rsp; @@ -955,7 +972,8 @@ qla2x00_abort_target(struct fc_port *fcport, unsigned int l, int tag) mcp->flags = 0; rval = qla2x00_mailbox_command(vha, mcp); if (rval != QLA_SUCCESS) { - ql_dbg(ql_dbg_mbx, vha, 0x103f, "Failed=%x.\n", rval); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x103f, + "Failed=%x.\n", rval); } /* Issue marker IOCB. */ @@ -965,7 +983,8 @@ qla2x00_abort_target(struct fc_port *fcport, unsigned int l, int tag) ql_dbg(ql_dbg_mbx, vha, 0x1040, "Failed to issue marker IOCB (%x).\n", rval2); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1041, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1041, + "Done %s.\n", __func__); } return rval; @@ -983,7 +1002,8 @@ qla2x00_lun_reset(struct fc_port *fcport, unsigned int l, int tag) vha = fcport->vha; - ql_dbg(ql_dbg_mbx, vha, 0x1042, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1042, + "Entered %s.\n", __func__); req = vha->hw->req_q_map[0]; rsp = req->rsp; @@ -1012,7 +1032,8 @@ qla2x00_lun_reset(struct fc_port *fcport, unsigned int l, int tag) ql_dbg(ql_dbg_mbx, vha, 0x1044, "Failed to issue marker IOCB (%x).\n", rval2); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1045, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1045, + "Done %s.\n", __func__); } return rval; @@ -1046,7 +1067,8 @@ qla2x00_get_adapter_id(scsi_qla_host_t *vha, uint16_t *id, uint8_t *al_pa, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1046, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1046, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_ADAPTER_LOOP_ID; mcp->mb[9] = vha->vp_idx; @@ -1074,7 +1096,8 @@ qla2x00_get_adapter_id(scsi_qla_host_t *vha, uint16_t *id, uint8_t *al_pa, /*EMPTY*/ ql_dbg(ql_dbg_mbx, vha, 0x1047, "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1048, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1048, + "Done %s.\n", __func__); if (IS_CNA_CAPABLE(vha->hw)) { vha->fcoe_vlan_id = mcp->mb[9] & 0xfff; @@ -1115,7 +1138,8 @@ qla2x00_get_retry_cnt(scsi_qla_host_t *vha, uint8_t *retry_cnt, uint8_t *tov, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1049, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1049, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_RETRY_COUNT; mcp->out_mb = MBX_0; @@ -1138,7 +1162,7 @@ qla2x00_get_retry_cnt(scsi_qla_host_t *vha, uint8_t *retry_cnt, uint8_t *tov, *tov = ratov; } - ql_dbg(ql_dbg_mbx, vha, 0x104b, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x104b, "Done %s mb3=%d ratov=%d.\n", __func__, mcp->mb[3], ratov); } @@ -1170,7 +1194,8 @@ qla2x00_init_firmware(scsi_qla_host_t *vha, uint16_t size) mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x104c, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x104c, + "Entered %s.\n", __func__); if (IS_QLA82XX(ha) && ql2xdbwr) qla82xx_wr_32(ha, ha->nxdb_wr_ptr, @@ -1213,9 +1238,100 @@ qla2x00_init_firmware(scsi_qla_host_t *vha, uint16_t size) rval, mcp->mb[0], mcp->mb[1], mcp->mb[2], mcp->mb[3]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x104e, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x104e, + "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qla2x00_get_node_name_list + * Issue get node name list mailbox command, kmalloc() + * and return the resulting list. Caller must kfree() it! + * + * Input: + * ha = adapter state pointer. + * out_data = resulting list + * out_len = length of the resulting list + * + * Returns: + * qla2x00 local function return status code. + * + * Context: + * Kernel context. + */ +int +qla2x00_get_node_name_list(scsi_qla_host_t *vha, void **out_data, int *out_len) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_port_24xx_data *list = NULL; + void *pmap; + mbx_cmd_t mc; + dma_addr_t pmap_dma; + ulong dma_size; + int rval, left; + + left = 1; + while (left > 0) { + dma_size = left * sizeof(*list); + pmap = dma_alloc_coherent(&ha->pdev->dev, dma_size, + &pmap_dma, GFP_KERNEL); + if (!pmap) { + ql_log(ql_log_warn, vha, 0x113f, + "%s(%ld): DMA Alloc failed of %ld\n", + __func__, vha->host_no, dma_size); + rval = QLA_MEMORY_ALLOC_FAILED; + goto out; + } + + mc.mb[0] = MBC_PORT_NODE_NAME_LIST; + mc.mb[1] = BIT_1 | BIT_3; + mc.mb[2] = MSW(pmap_dma); + mc.mb[3] = LSW(pmap_dma); + mc.mb[6] = MSW(MSD(pmap_dma)); + mc.mb[7] = LSW(MSD(pmap_dma)); + mc.mb[8] = dma_size; + mc.out_mb = MBX_0|MBX_1|MBX_2|MBX_3|MBX_6|MBX_7|MBX_8; + mc.in_mb = MBX_0|MBX_1; + mc.tov = 30; + mc.flags = MBX_DMA_IN; + + rval = qla2x00_mailbox_command(vha, &mc); + if (rval != QLA_SUCCESS) { + if ((mc.mb[0] == MBS_COMMAND_ERROR) && + (mc.mb[1] == 0xA)) { + left += le16_to_cpu(mc.mb[2]) / + sizeof(struct qla_port_24xx_data); + goto restart; + } + goto out_free; + } + + left = 0; + + list = kzalloc(dma_size, GFP_KERNEL); + if (!list) { + ql_log(ql_log_warn, vha, 0x1140, + "%s(%ld): failed to allocate node names list " + "structure.\n", __func__, vha->host_no); + rval = QLA_MEMORY_ALLOC_FAILED; + goto out_free; + } + + memcpy(list, pmap, dma_size); +restart: + dma_free_coherent(&ha->pdev->dev, dma_size, pmap, pmap_dma); } + *out_data = list; + *out_len = dma_size; + +out: + return rval; + +out_free: + dma_free_coherent(&ha->pdev->dev, dma_size, pmap, pmap_dma); return rval; } @@ -1246,7 +1362,8 @@ qla2x00_get_port_database(scsi_qla_host_t *vha, fc_port_t *fcport, uint8_t opt) dma_addr_t pd_dma; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x104f, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x104f, + "Entered %s.\n", __func__); pd24 = NULL; pd = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &pd_dma); @@ -1326,6 +1443,13 @@ qla2x00_get_port_database(scsi_qla_host_t *vha, fc_port_t *fcport, uint8_t opt) fcport->port_type = FCT_INITIATOR; else fcport->port_type = FCT_TARGET; + + /* Passback COS information. */ + fcport->supported_classes = (pd24->flags & PDF_CLASS_2) ? + FC_COS_CLASS2 : FC_COS_CLASS3; + + if (pd24->prli_svc_param_word_3[0] & BIT_7) + fcport->flags |= FCF_CONF_COMP_SUPPORTED; } else { uint64_t zero = 0; @@ -1378,7 +1502,8 @@ gpd_error_out: "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1053, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1053, + "Done %s.\n", __func__); } return rval; @@ -1407,7 +1532,8 @@ qla2x00_get_firmware_state(scsi_qla_host_t *vha, uint16_t *states) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1054, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1054, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_FIRMWARE_STATE; mcp->out_mb = MBX_0; @@ -1433,7 +1559,8 @@ qla2x00_get_firmware_state(scsi_qla_host_t *vha, uint16_t *states) ql_dbg(ql_dbg_mbx, vha, 0x1055, "Failed=%x.\n", rval); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1056, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1056, + "Done %s.\n", __func__); } return rval; @@ -1465,7 +1592,8 @@ qla2x00_get_port_name(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t *name, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1057, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1057, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_PORT_NAME; mcp->mb[9] = vha->vp_idx; @@ -1499,7 +1627,8 @@ qla2x00_get_port_name(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t *name, name[7] = LSB(mcp->mb[7]); } - ql_dbg(ql_dbg_mbx, vha, 0x1059, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1059, + "Done %s.\n", __func__); } return rval; @@ -1527,7 +1656,8 @@ qla2x00_lip_reset(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x105a, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x105a, + "Entered %s.\n", __func__); if (IS_CNA_CAPABLE(vha->hw)) { /* Logout across all FCFs. */ @@ -1564,7 +1694,8 @@ qla2x00_lip_reset(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx, vha, 0x105b, "Failed=%x.\n", rval); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x105c, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x105c, + "Done %s.\n", __func__); } return rval; @@ -1596,9 +1727,10 @@ qla2x00_send_sns(scsi_qla_host_t *vha, dma_addr_t sns_phys_address, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x105d, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x105d, + "Entered %s.\n", __func__); - ql_dbg(ql_dbg_mbx, vha, 0x105e, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x105e, "Retry cnt=%d ratov=%d total tov=%d.\n", vha->hw->retry_count, vha->hw->login_timeout, mcp->tov); @@ -1622,7 +1754,8 @@ qla2x00_send_sns(scsi_qla_host_t *vha, dma_addr_t sns_phys_address, rval, mcp->mb[0], mcp->mb[1]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1060, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1060, + "Done %s.\n", __func__); } return rval; @@ -1641,7 +1774,8 @@ qla24xx_login_fabric(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, struct req_que *req; struct rsp_que *rsp; - ql_dbg(ql_dbg_mbx, vha, 0x1061, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1061, + "Entered %s.\n", __func__); if (ha->flags.cpu_affinity_enabled) req = ha->req_q_map[0]; @@ -1715,7 +1849,8 @@ qla24xx_login_fabric(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, break; } } else { - ql_dbg(ql_dbg_mbx, vha, 0x1066, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1066, + "Done %s.\n", __func__); iop[0] = le32_to_cpu(lg->io_parameter[0]); @@ -1733,6 +1868,10 @@ qla24xx_login_fabric(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, mb[10] |= BIT_0; /* Class 2. */ if (lg->io_parameter[9] || lg->io_parameter[10]) mb[10] |= BIT_1; /* Class 3. */ + if (lg->io_parameter[0] & __constant_cpu_to_le32(BIT_7)) + mb[10] |= BIT_7; /* Confirmed Completion + * Allowed + */ } dma_pool_free(ha->s_dma_pool, lg, lg_dma); @@ -1770,7 +1909,8 @@ qla2x00_login_fabric(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x1067, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1067, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_LOGIN_FABRIC_PORT; mcp->out_mb = MBX_3|MBX_2|MBX_1|MBX_0; @@ -1818,7 +1958,8 @@ qla2x00_login_fabric(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, rval, mcp->mb[0], mcp->mb[1], mcp->mb[2]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1069, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1069, + "Done %s.\n", __func__); } return rval; @@ -1849,7 +1990,8 @@ qla2x00_login_local_device(scsi_qla_host_t *vha, fc_port_t *fcport, mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x106a, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x106a, + "Entered %s.\n", __func__); if (IS_FWI2_CAPABLE(ha)) return qla24xx_login_fabric(vha, fcport->loop_id, @@ -1891,7 +2033,8 @@ qla2x00_login_local_device(scsi_qla_host_t *vha, fc_port_t *fcport, rval, mcp->mb[0], mcp->mb[1], mcp->mb[6], mcp->mb[7]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x106c, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x106c, + "Done %s.\n", __func__); } return (rval); @@ -1908,7 +2051,8 @@ qla24xx_fabric_logout(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, struct req_que *req; struct rsp_que *rsp; - ql_dbg(ql_dbg_mbx, vha, 0x106d, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x106d, + "Entered %s.\n", __func__); lg = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &lg_dma); if (lg == NULL) { @@ -1952,7 +2096,8 @@ qla24xx_fabric_logout(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, le32_to_cpu(lg->io_parameter[1])); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1072, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1072, + "Done %s.\n", __func__); } dma_pool_free(ha->s_dma_pool, lg, lg_dma); @@ -1984,7 +2129,8 @@ qla2x00_fabric_logout(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1073, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1073, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_LOGOUT_FABRIC_PORT; mcp->out_mb = MBX_1|MBX_0; @@ -2007,7 +2153,8 @@ qla2x00_fabric_logout(scsi_qla_host_t *vha, uint16_t loop_id, uint8_t domain, "Failed=%x mb[1]=%x.\n", rval, mcp->mb[1]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1075, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1075, + "Done %s.\n", __func__); } return rval; @@ -2035,7 +2182,8 @@ qla2x00_full_login_lip(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1076, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1076, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_LIP_FULL_LOGIN; mcp->mb[1] = IS_FWI2_CAPABLE(vha->hw) ? BIT_3 : 0; @@ -2052,7 +2200,8 @@ qla2x00_full_login_lip(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx, vha, 0x1077, "Failed=%x.\n", rval); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x1078, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1078, + "Done %s.\n", __func__); } return rval; @@ -2078,7 +2227,8 @@ qla2x00_get_id_list(scsi_qla_host_t *vha, void *id_list, dma_addr_t id_list_dma, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1079, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1079, + "Entered %s.\n", __func__); if (id_list == NULL) return QLA_FUNCTION_FAILED; @@ -2110,7 +2260,8 @@ qla2x00_get_id_list(scsi_qla_host_t *vha, void *id_list, dma_addr_t id_list_dma, ql_dbg(ql_dbg_mbx, vha, 0x107a, "Failed=%x.\n", rval); } else { *entries = mcp->mb[1]; - ql_dbg(ql_dbg_mbx, vha, 0x107b, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x107b, + "Done %s.\n", __func__); } return rval; @@ -2138,7 +2289,8 @@ qla2x00_get_resource_cnts(scsi_qla_host_t *vha, uint16_t *cur_xchg_cnt, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x107c, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x107c, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_RESOURCE_COUNTS; mcp->out_mb = MBX_0; @@ -2154,7 +2306,7 @@ qla2x00_get_resource_cnts(scsi_qla_host_t *vha, uint16_t *cur_xchg_cnt, ql_dbg(ql_dbg_mbx, vha, 0x107d, "Failed mb[0]=%x.\n", mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x107e, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x107e, "Done %s mb1=%x mb2=%x mb3=%x mb6=%x mb7=%x mb10=%x " "mb11=%x mb12=%x.\n", __func__, mcp->mb[1], mcp->mb[2], mcp->mb[3], mcp->mb[6], mcp->mb[7], mcp->mb[10], @@ -2201,7 +2353,8 @@ qla2x00_get_fcal_position_map(scsi_qla_host_t *vha, char *pos_map) dma_addr_t pmap_dma; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x107f, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x107f, + "Entered %s.\n", __func__); pmap = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &pmap_dma); if (pmap == NULL) { @@ -2224,7 +2377,7 @@ qla2x00_get_fcal_position_map(scsi_qla_host_t *vha, char *pos_map) rval = qla2x00_mailbox_command(vha, mcp); if (rval == QLA_SUCCESS) { - ql_dbg(ql_dbg_mbx, vha, 0x1081, + ql_dbg(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1081, "mb0/mb1=%x/%X FC/AL position map size (%x).\n", mcp->mb[0], mcp->mb[1], (unsigned)pmap[0]); ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x111d, @@ -2238,7 +2391,8 @@ qla2x00_get_fcal_position_map(scsi_qla_host_t *vha, char *pos_map) if (rval != QLA_SUCCESS) { ql_dbg(ql_dbg_mbx, vha, 0x1082, "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1083, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1083, + "Done %s.\n", __func__); } return rval; @@ -2267,7 +2421,8 @@ qla2x00_get_link_status(scsi_qla_host_t *vha, uint16_t loop_id, uint32_t *siter, *diter, dwords; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x1084, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1084, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_LINK_STATUS; mcp->mb[2] = MSW(stats_dma); @@ -2301,7 +2456,8 @@ qla2x00_get_link_status(scsi_qla_host_t *vha, uint16_t loop_id, rval = QLA_FUNCTION_FAILED; } else { /* Copy over data -- firmware data is LE. */ - ql_dbg(ql_dbg_mbx, vha, 0x1086, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1086, + "Done %s.\n", __func__); dwords = offsetof(struct link_statistics, unused1) / 4; siter = diter = &stats->link_fail_cnt; while (dwords--) @@ -2324,7 +2480,8 @@ qla24xx_get_isp_stats(scsi_qla_host_t *vha, struct link_statistics *stats, mbx_cmd_t *mcp = &mc; uint32_t *siter, *diter, dwords; - ql_dbg(ql_dbg_mbx, vha, 0x1088, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1088, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GET_LINK_PRIV_STATS; mcp->mb[2] = MSW(stats_dma); @@ -2346,7 +2503,8 @@ qla24xx_get_isp_stats(scsi_qla_host_t *vha, struct link_statistics *stats, "Failed mb[0]=%x.\n", mcp->mb[0]); rval = QLA_FUNCTION_FAILED; } else { - ql_dbg(ql_dbg_mbx, vha, 0x108a, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x108a, + "Done %s.\n", __func__); /* Copy over data -- firmware data is LE. */ dwords = sizeof(struct link_statistics) / 4; siter = diter = &stats->link_fail_cnt; @@ -2375,7 +2533,8 @@ qla24xx_abort_command(srb_t *sp) struct qla_hw_data *ha = vha->hw; struct req_que *req = vha->req; - ql_dbg(ql_dbg_mbx, vha, 0x108c, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x108c, + "Entered %s.\n", __func__); spin_lock_irqsave(&ha->hardware_lock, flags); for (handle = 1; handle < MAX_OUTSTANDING_COMMANDS; handle++) { @@ -2404,7 +2563,7 @@ qla24xx_abort_command(srb_t *sp) abt->port_id[0] = fcport->d_id.b.al_pa; abt->port_id[1] = fcport->d_id.b.area; abt->port_id[2] = fcport->d_id.b.domain; - abt->vp_index = fcport->vp_idx; + abt->vp_index = fcport->vha->vp_idx; abt->req_que_no = cpu_to_le16(req->id); @@ -2423,7 +2582,8 @@ qla24xx_abort_command(srb_t *sp) le16_to_cpu(abt->nport_handle)); rval = QLA_FUNCTION_FAILED; } else { - ql_dbg(ql_dbg_mbx, vha, 0x1091, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1091, + "Done %s.\n", __func__); } dma_pool_free(ha->s_dma_pool, abt, abt_dma); @@ -2455,7 +2615,8 @@ __qla24xx_issue_tmf(char *name, uint32_t type, struct fc_port *fcport, ha = vha->hw; req = vha->req; - ql_dbg(ql_dbg_mbx, vha, 0x1092, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1092, + "Entered %s.\n", __func__); if (ha->flags.cpu_affinity_enabled) rsp = ha->rsp_q_map[tag + 1]; @@ -2478,7 +2639,7 @@ __qla24xx_issue_tmf(char *name, uint32_t type, struct fc_port *fcport, tsk->p.tsk.port_id[0] = fcport->d_id.b.al_pa; tsk->p.tsk.port_id[1] = fcport->d_id.b.area; tsk->p.tsk.port_id[2] = fcport->d_id.b.domain; - tsk->p.tsk.vp_index = fcport->vp_idx; + tsk->p.tsk.vp_index = fcport->vha->vp_idx; if (type == TCF_LUN_RESET) { int_to_scsilun(l, &tsk->p.tsk.lun); host_to_fcp_swap((uint8_t *)&tsk->p.tsk.lun, @@ -2504,7 +2665,7 @@ __qla24xx_issue_tmf(char *name, uint32_t type, struct fc_port *fcport, } else if (le16_to_cpu(sts->scsi_status) & SS_RESPONSE_INFO_LEN_VALID) { if (le32_to_cpu(sts->rsp_data_len) < 4) { - ql_dbg(ql_dbg_mbx, vha, 0x1097, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1097, "Ignoring inconsistent data length -- not enough " "response info (%d).\n", le32_to_cpu(sts->rsp_data_len)); @@ -2523,7 +2684,8 @@ __qla24xx_issue_tmf(char *name, uint32_t type, struct fc_port *fcport, ql_dbg(ql_dbg_mbx, vha, 0x1099, "Failed to issue marker IOCB (%x).\n", rval2); } else { - ql_dbg(ql_dbg_mbx, vha, 0x109a, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x109a, + "Done %s.\n", __func__); } dma_pool_free(ha->s_dma_pool, tsk, tsk_dma); @@ -2564,7 +2726,8 @@ qla2x00_system_error(scsi_qla_host_t *vha) if (!IS_QLA23XX(ha) && !IS_FWI2_CAPABLE(ha)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x109b, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x109b, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_GEN_SYSTEM_ERROR; mcp->out_mb = MBX_0; @@ -2576,7 +2739,8 @@ qla2x00_system_error(scsi_qla_host_t *vha) if (rval != QLA_SUCCESS) { ql_dbg(ql_dbg_mbx, vha, 0x109c, "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x109d, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x109d, + "Done %s.\n", __func__); } return rval; @@ -2596,7 +2760,8 @@ qla2x00_set_serdes_params(scsi_qla_host_t *vha, uint16_t sw_em_1g, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x109e, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x109e, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_SERDES_PARAMS; mcp->mb[1] = BIT_0; @@ -2615,7 +2780,8 @@ qla2x00_set_serdes_params(scsi_qla_host_t *vha, uint16_t sw_em_1g, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { /*EMPTY*/ - ql_dbg(ql_dbg_mbx, vha, 0x10a0, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a0, + "Done %s.\n", __func__); } return rval; @@ -2631,7 +2797,8 @@ qla2x00_stop_firmware(scsi_qla_host_t *vha) if (!IS_FWI2_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x10a1, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a1, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_STOP_FIRMWARE; mcp->mb[1] = 0; @@ -2646,7 +2813,8 @@ qla2x00_stop_firmware(scsi_qla_host_t *vha) if (mcp->mb[0] == MBS_INVALID_COMMAND) rval = QLA_INVALID_COMMAND; } else { - ql_dbg(ql_dbg_mbx, vha, 0x10a3, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a3, + "Done %s.\n", __func__); } return rval; @@ -2660,7 +2828,8 @@ qla2x00_enable_eft_trace(scsi_qla_host_t *vha, dma_addr_t eft_dma, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10a4, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a4, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -2686,7 +2855,8 @@ qla2x00_enable_eft_trace(scsi_qla_host_t *vha, dma_addr_t eft_dma, "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10a6, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a6, + "Done %s.\n", __func__); } return rval; @@ -2699,7 +2869,8 @@ qla2x00_disable_eft_trace(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10a7, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a7, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -2719,7 +2890,8 @@ qla2x00_disable_eft_trace(scsi_qla_host_t *vha) "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10a9, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10a9, + "Done %s.\n", __func__); } return rval; @@ -2733,7 +2905,8 @@ qla2x00_enable_fce_trace(scsi_qla_host_t *vha, dma_addr_t fce_dma, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10aa, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10aa, + "Entered %s.\n", __func__); if (!IS_QLA25XX(vha->hw) && !IS_QLA81XX(vha->hw) && !IS_QLA83XX(vha->hw)) @@ -2764,7 +2937,8 @@ qla2x00_enable_fce_trace(scsi_qla_host_t *vha, dma_addr_t fce_dma, "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10ac, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ac, + "Done %s.\n", __func__); if (mb) memcpy(mb, mcp->mb, 8 * sizeof(*mb)); @@ -2782,7 +2956,8 @@ qla2x00_disable_fce_trace(scsi_qla_host_t *vha, uint64_t *wr, uint64_t *rd) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10ad, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ad, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -2804,7 +2979,8 @@ qla2x00_disable_fce_trace(scsi_qla_host_t *vha, uint64_t *wr, uint64_t *rd) "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10af, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10af, + "Done %s.\n", __func__); if (wr) *wr = (uint64_t) mcp->mb[5] << 48 | @@ -2829,7 +3005,8 @@ qla2x00_get_idma_speed(scsi_qla_host_t *vha, uint16_t loop_id, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10b0, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b0, + "Entered %s.\n", __func__); if (!IS_IIDMA_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -2854,7 +3031,8 @@ qla2x00_get_idma_speed(scsi_qla_host_t *vha, uint16_t loop_id, if (rval != QLA_SUCCESS) { ql_dbg(ql_dbg_mbx, vha, 0x10b1, "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10b2, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b2, + "Done %s.\n", __func__); if (port_speed) *port_speed = mcp->mb[3]; } @@ -2870,7 +3048,8 @@ qla2x00_set_idma_speed(scsi_qla_host_t *vha, uint16_t loop_id, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10b3, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b3, + "Entered %s.\n", __func__); if (!IS_IIDMA_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -2897,9 +3076,11 @@ qla2x00_set_idma_speed(scsi_qla_host_t *vha, uint16_t loop_id, } if (rval != QLA_SUCCESS) { - ql_dbg(ql_dbg_mbx, vha, 0x10b4, "Failed=%x.\n", rval); + ql_dbg(ql_dbg_mbx, vha, 0x10b4, + "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10b5, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b5, + "Done %s.\n", __func__); } return rval; @@ -2915,24 +3096,25 @@ qla24xx_report_id_acquisition(scsi_qla_host_t *vha, scsi_qla_host_t *vp; unsigned long flags; - ql_dbg(ql_dbg_mbx, vha, 0x10b6, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b6, + "Entered %s.\n", __func__); if (rptid_entry->entry_status != 0) return; if (rptid_entry->format == 0) { - ql_dbg(ql_dbg_mbx, vha, 0x10b7, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b7, "Format 0 : Number of VPs setup %d, number of " "VPs acquired %d.\n", MSB(le16_to_cpu(rptid_entry->vp_count)), LSB(le16_to_cpu(rptid_entry->vp_count))); - ql_dbg(ql_dbg_mbx, vha, 0x10b8, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b8, "Primary port id %02x%02x%02x.\n", rptid_entry->port_id[2], rptid_entry->port_id[1], rptid_entry->port_id[0]); } else if (rptid_entry->format == 1) { vp_idx = LSB(stat); - ql_dbg(ql_dbg_mbx, vha, 0x10b9, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10b9, "Format 1: VP[%d] enabled - status %d - with " "port id %02x%02x%02x.\n", vp_idx, MSB(stat), rptid_entry->port_id[2], rptid_entry->port_id[1], @@ -2999,7 +3181,8 @@ qla24xx_modify_vp_config(scsi_qla_host_t *vha) /* This can be called by the parent */ - ql_dbg(ql_dbg_mbx, vha, 0x10bb, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10bb, + "Entered %s.\n", __func__); vpmod = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &vpmod_dma); if (!vpmod) { @@ -3015,6 +3198,9 @@ qla24xx_modify_vp_config(scsi_qla_host_t *vha) vpmod->vp_count = 1; vpmod->vp_index1 = vha->vp_idx; vpmod->options_idx1 = BIT_3|BIT_4|BIT_5; + + qlt_modify_vp_config(vha, vpmod); + memcpy(vpmod->node_name_idx1, vha->node_name, WWN_SIZE); memcpy(vpmod->port_name_idx1, vha->port_name, WWN_SIZE); vpmod->entry_count = 1; @@ -3035,7 +3221,8 @@ qla24xx_modify_vp_config(scsi_qla_host_t *vha) rval = QLA_FUNCTION_FAILED; } else { /* EMPTY */ - ql_dbg(ql_dbg_mbx, vha, 0x10c0, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10c0, + "Done %s.\n", __func__); fc_vport_set_state(vha->fc_vport, FC_VPORT_INITIALIZING); } dma_pool_free(ha->s_dma_pool, vpmod, vpmod_dma); @@ -3069,7 +3256,7 @@ qla24xx_control_vp(scsi_qla_host_t *vha, int cmd) int vp_index = vha->vp_idx; struct scsi_qla_host *base_vha = pci_get_drvdata(ha->pdev); - ql_dbg(ql_dbg_mbx, vha, 0x10c1, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10c1, "Entered %s enabling index %d.\n", __func__, vp_index); if (vp_index == 0 || vp_index >= ha->max_npiv_vports) @@ -3112,7 +3299,8 @@ qla24xx_control_vp(scsi_qla_host_t *vha, int cmd) le16_to_cpu(vce->comp_status)); rval = QLA_FUNCTION_FAILED; } else { - ql_dbg(ql_dbg_mbx, vha, 0x10c6, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10c6, + "Done %s.\n", __func__); } dma_pool_free(ha->s_dma_pool, vce, vce_dma); @@ -3149,14 +3337,8 @@ qla2x00_send_change_request(scsi_qla_host_t *vha, uint16_t format, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10c7, "Entered %s.\n", __func__); - - /* - * This command is implicitly executed by firmware during login for the - * physical hosts - */ - if (vp_idx == 0) - return QLA_FUNCTION_FAILED; + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10c7, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_SEND_CHANGE_REQUEST; mcp->mb[1] = format; @@ -3185,7 +3367,8 @@ qla2x00_dump_ram(scsi_qla_host_t *vha, dma_addr_t req_dma, uint32_t addr, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1009, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1009, + "Entered %s.\n", __func__); if (MSW(addr) || IS_FWI2_CAPABLE(vha->hw)) { mcp->mb[0] = MBC_DUMP_RISC_RAM_EXTENDED; @@ -3219,7 +3402,8 @@ qla2x00_dump_ram(scsi_qla_host_t *vha, dma_addr_t req_dma, uint32_t addr, ql_dbg(ql_dbg_mbx, vha, 0x1008, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1007, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1007, + "Done %s.\n", __func__); } return rval; @@ -3244,7 +3428,8 @@ qla84xx_verify_chip(struct scsi_qla_host *vha, uint16_t *status) unsigned long flags; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10c8, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10c8, + "Entered %s.\n", __func__); mn = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &mn_dma); if (mn == NULL) { @@ -3285,7 +3470,7 @@ qla84xx_verify_chip(struct scsi_qla_host *vha, uint16_t *status) status[0] = le16_to_cpu(mn->p.rsp.comp_status); status[1] = status[0] == CS_VCS_CHIP_FAILURE ? le16_to_cpu(mn->p.rsp.failure_code) : 0; - ql_dbg(ql_dbg_mbx, vha, 0x10ce, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ce, "cs=%x fc=%x.\n", status[0], status[1]); if (status[0] != CS_COMPLETE) { @@ -3299,7 +3484,7 @@ qla84xx_verify_chip(struct scsi_qla_host *vha, uint16_t *status) retry = 1; } } else { - ql_dbg(ql_dbg_mbx, vha, 0x10d0, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d0, "Firmware updated to %x.\n", le32_to_cpu(mn->p.rsp.fw_ver)); @@ -3316,9 +3501,11 @@ verify_done: dma_pool_free(ha->s_dma_pool, mn, mn_dma); if (rval != QLA_SUCCESS) { - ql_dbg(ql_dbg_mbx, vha, 0x10d1, "Failed=%x.\n", rval); + ql_dbg(ql_dbg_mbx, vha, 0x10d1, + "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10d2, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d2, + "Done %s.\n", __func__); } return rval; @@ -3334,7 +3521,8 @@ qla25xx_init_req_que(struct scsi_qla_host *vha, struct req_que *req) struct device_reg_25xxmq __iomem *reg; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10d3, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d3, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_INITIALIZE_MULTIQ; mcp->mb[1] = req->options; @@ -3388,7 +3576,8 @@ qla25xx_init_req_que(struct scsi_qla_host *vha, struct req_que *req) ql_dbg(ql_dbg_mbx, vha, 0x10d4, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10d5, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d5, + "Done %s.\n", __func__); } return rval; @@ -3404,7 +3593,8 @@ qla25xx_init_rsp_que(struct scsi_qla_host *vha, struct rsp_que *rsp) struct device_reg_25xxmq __iomem *reg; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10d6, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d6, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_INITIALIZE_MULTIQ; mcp->mb[1] = rsp->options; @@ -3456,7 +3646,8 @@ qla25xx_init_rsp_que(struct scsi_qla_host *vha, struct rsp_que *rsp) ql_dbg(ql_dbg_mbx, vha, 0x10d7, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10d8, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d8, + "Done %s.\n", __func__); } return rval; @@ -3469,7 +3660,8 @@ qla81xx_idc_ack(scsi_qla_host_t *vha, uint16_t *mb) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10d9, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10d9, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_IDC_ACK; memcpy(&mcp->mb[1], mb, QLA_IDC_ACK_REGS * sizeof(uint16_t)); @@ -3483,7 +3675,8 @@ qla81xx_idc_ack(scsi_qla_host_t *vha, uint16_t *mb) ql_dbg(ql_dbg_mbx, vha, 0x10da, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10db, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10db, + "Done %s.\n", __func__); } return rval; @@ -3496,7 +3689,8 @@ qla81xx_fac_get_sector_size(scsi_qla_host_t *vha, uint32_t *sector_size) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10dc, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10dc, + "Entered %s.\n", __func__); if (!IS_QLA81XX(vha->hw) && !IS_QLA83XX(vha->hw)) return QLA_FUNCTION_FAILED; @@ -3514,7 +3708,8 @@ qla81xx_fac_get_sector_size(scsi_qla_host_t *vha, uint32_t *sector_size) "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10de, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10de, + "Done %s.\n", __func__); *sector_size = mcp->mb[1]; } @@ -3531,7 +3726,8 @@ qla81xx_fac_do_write_enable(scsi_qla_host_t *vha, int enable) if (!IS_QLA81XX(vha->hw) && !IS_QLA83XX(vha->hw)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x10df, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10df, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_FLASH_ACCESS_CTRL; mcp->mb[1] = enable ? FAC_OPT_CMD_WRITE_ENABLE : @@ -3547,7 +3743,8 @@ qla81xx_fac_do_write_enable(scsi_qla_host_t *vha, int enable) "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10e1, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10e1, + "Done %s.\n", __func__); } return rval; @@ -3563,7 +3760,8 @@ qla81xx_fac_erase_sector(scsi_qla_host_t *vha, uint32_t start, uint32_t finish) if (!IS_QLA81XX(vha->hw) && !IS_QLA83XX(vha->hw)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x10e2, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10e2, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_FLASH_ACCESS_CTRL; mcp->mb[1] = FAC_OPT_CMD_ERASE_SECTOR; @@ -3582,7 +3780,8 @@ qla81xx_fac_erase_sector(scsi_qla_host_t *vha, uint32_t start, uint32_t finish) "Failed=%x mb[0]=%x mb[1]=%x mb[2]=%x.\n", rval, mcp->mb[0], mcp->mb[1], mcp->mb[2]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10e4, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10e4, + "Done %s.\n", __func__); } return rval; @@ -3595,7 +3794,8 @@ qla81xx_restart_mpi_firmware(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10e5, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10e5, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_RESTART_MPI_FW; mcp->out_mb = MBX_0; @@ -3609,7 +3809,8 @@ qla81xx_restart_mpi_firmware(scsi_qla_host_t *vha) "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10e7, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10e7, + "Done %s.\n", __func__); } return rval; @@ -3624,7 +3825,8 @@ qla2x00_read_sfp(scsi_qla_host_t *vha, dma_addr_t sfp_dma, uint8_t *sfp, mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10e8, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10e8, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(ha)) return QLA_FUNCTION_FAILED; @@ -3654,7 +3856,8 @@ qla2x00_read_sfp(scsi_qla_host_t *vha, dma_addr_t sfp_dma, uint8_t *sfp, ql_dbg(ql_dbg_mbx, vha, 0x10e9, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10ea, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ea, + "Done %s.\n", __func__); } return rval; @@ -3669,7 +3872,8 @@ qla2x00_write_sfp(scsi_qla_host_t *vha, dma_addr_t sfp_dma, uint8_t *sfp, mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10eb, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10eb, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(ha)) return QLA_FUNCTION_FAILED; @@ -3699,7 +3903,8 @@ qla2x00_write_sfp(scsi_qla_host_t *vha, dma_addr_t sfp_dma, uint8_t *sfp, ql_dbg(ql_dbg_mbx, vha, 0x10ec, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10ed, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ed, + "Done %s.\n", __func__); } return rval; @@ -3713,7 +3918,8 @@ qla2x00_get_xgmac_stats(scsi_qla_host_t *vha, dma_addr_t stats_dma, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10ee, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ee, + "Entered %s.\n", __func__); if (!IS_CNA_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -3735,7 +3941,8 @@ qla2x00_get_xgmac_stats(scsi_qla_host_t *vha, dma_addr_t stats_dma, "Failed=%x mb[0]=%x mb[1]=%x mb[2]=%x.\n", rval, mcp->mb[0], mcp->mb[1], mcp->mb[2]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10f0, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f0, + "Done %s.\n", __func__); *actual_size = mcp->mb[2] << 2; @@ -3752,7 +3959,8 @@ qla2x00_get_dcbx_params(scsi_qla_host_t *vha, dma_addr_t tlv_dma, mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10f1, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f1, + "Entered %s.\n", __func__); if (!IS_CNA_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -3775,7 +3983,8 @@ qla2x00_get_dcbx_params(scsi_qla_host_t *vha, dma_addr_t tlv_dma, "Failed=%x mb[0]=%x mb[1]=%x mb[2]=%x.\n", rval, mcp->mb[0], mcp->mb[1], mcp->mb[2]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10f3, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f3, + "Done %s.\n", __func__); } return rval; @@ -3788,7 +3997,8 @@ qla2x00_read_ram_word(scsi_qla_host_t *vha, uint32_t risc_addr, uint32_t *data) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10f4, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f4, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -3805,7 +4015,8 @@ qla2x00_read_ram_word(scsi_qla_host_t *vha, uint32_t risc_addr, uint32_t *data) ql_dbg(ql_dbg_mbx, vha, 0x10f5, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10f6, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f6, + "Done %s.\n", __func__); *data = mcp->mb[3] << 16 | mcp->mb[2]; } @@ -3821,7 +4032,8 @@ qla2x00_loopback_test(scsi_qla_host_t *vha, struct msg_echo_lb *mreq, mbx_cmd_t *mcp = &mc; uint32_t iter_cnt = 0x1; - ql_dbg(ql_dbg_mbx, vha, 0x10f7, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f7, + "Entered %s.\n", __func__); memset(mcp->mb, 0 , sizeof(mcp->mb)); mcp->mb[0] = MBC_DIAGNOSTIC_LOOP_BACK; @@ -3865,7 +4077,8 @@ qla2x00_loopback_test(scsi_qla_host_t *vha, struct msg_echo_lb *mreq, "mb[19]=%x.\n", rval, mcp->mb[0], mcp->mb[1], mcp->mb[2], mcp->mb[3], mcp->mb[18], mcp->mb[19]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10f9, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f9, + "Done %s.\n", __func__); } /* Copy mailbox information */ @@ -3882,7 +4095,8 @@ qla2x00_echo_test(scsi_qla_host_t *vha, struct msg_echo_lb *mreq, mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10fa, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10fa, + "Entered %s.\n", __func__); memset(mcp->mb, 0 , sizeof(mcp->mb)); mcp->mb[0] = MBC_DIAGNOSTIC_ECHO; @@ -3926,7 +4140,8 @@ qla2x00_echo_test(scsi_qla_host_t *vha, struct msg_echo_lb *mreq, "Failed=%x mb[0]=%x mb[1]=%x.\n", rval, mcp->mb[0], mcp->mb[1]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10fc, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10fc, + "Done %s.\n", __func__); } /* Copy mailbox information */ @@ -3941,7 +4156,7 @@ qla84xx_reset_chip(scsi_qla_host_t *vha, uint16_t enable_diagnostic) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x10fd, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10fd, "Entered %s enable_diag=%d.\n", __func__, enable_diagnostic); mcp->mb[0] = MBC_ISP84XX_RESET; @@ -3955,7 +4170,8 @@ qla84xx_reset_chip(scsi_qla_host_t *vha, uint16_t enable_diagnostic) if (rval != QLA_SUCCESS) ql_dbg(ql_dbg_mbx, vha, 0x10fe, "Failed=%x.\n", rval); else - ql_dbg(ql_dbg_mbx, vha, 0x10ff, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ff, + "Done %s.\n", __func__); return rval; } @@ -3967,7 +4183,8 @@ qla2x00_write_ram_word(scsi_qla_host_t *vha, uint32_t risc_addr, uint32_t data) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1100, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1100, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(vha->hw)) return QLA_FUNCTION_FAILED; @@ -3986,7 +4203,8 @@ qla2x00_write_ram_word(scsi_qla_host_t *vha, uint32_t risc_addr, uint32_t data) ql_dbg(ql_dbg_mbx, vha, 0x1101, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1102, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1102, + "Done %s.\n", __func__); } return rval; @@ -4003,7 +4221,8 @@ qla81xx_write_mpi_register(scsi_qla_host_t *vha, uint16_t *mb) rval = QLA_SUCCESS; - ql_dbg(ql_dbg_mbx, vha, 0x1103, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1103, + "Entered %s.\n", __func__); clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); @@ -4046,7 +4265,8 @@ qla81xx_write_mpi_register(scsi_qla_host_t *vha, uint16_t *mb) ql_dbg(ql_dbg_mbx, vha, 0x1104, "Failed=%x mb[0]=%x.\n", rval, mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1105, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1105, + "Done %s.\n", __func__); } return rval; @@ -4060,7 +4280,8 @@ qla2x00_get_data_rate(scsi_qla_host_t *vha) mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x1106, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1106, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(ha)) return QLA_FUNCTION_FAILED; @@ -4078,7 +4299,8 @@ qla2x00_get_data_rate(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx, vha, 0x1107, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1108, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1108, + "Done %s.\n", __func__); if (mcp->mb[1] != 0x7) ha->link_data_rate = mcp->mb[1]; } @@ -4094,7 +4316,8 @@ qla81xx_get_port_config(scsi_qla_host_t *vha, uint16_t *mb) mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x1109, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1109, + "Entered %s.\n", __func__); if (!IS_QLA81XX(ha) && !IS_QLA83XX(ha)) return QLA_FUNCTION_FAILED; @@ -4113,7 +4336,8 @@ qla81xx_get_port_config(scsi_qla_host_t *vha, uint16_t *mb) /* Copy all bits to preserve original value */ memcpy(mb, &mcp->mb[1], sizeof(uint16_t) * 4); - ql_dbg(ql_dbg_mbx, vha, 0x110b, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x110b, + "Done %s.\n", __func__); } return rval; } @@ -4125,7 +4349,8 @@ qla81xx_set_port_config(scsi_qla_host_t *vha, uint16_t *mb) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x110c, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x110c, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_SET_PORT_CONFIG; /* Copy all bits to preserve original setting */ @@ -4140,7 +4365,8 @@ qla81xx_set_port_config(scsi_qla_host_t *vha, uint16_t *mb) ql_dbg(ql_dbg_mbx, vha, 0x110d, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else - ql_dbg(ql_dbg_mbx, vha, 0x110e, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x110e, + "Done %s.\n", __func__); return rval; } @@ -4155,7 +4381,8 @@ qla24xx_set_fcp_prio(scsi_qla_host_t *vha, uint16_t loop_id, uint16_t priority, mbx_cmd_t *mcp = &mc; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x110f, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x110f, + "Entered %s.\n", __func__); if (!IS_QLA24XX_TYPE(ha) && !IS_QLA25XX(ha)) return QLA_FUNCTION_FAILED; @@ -4183,7 +4410,8 @@ qla24xx_set_fcp_prio(scsi_qla_host_t *vha, uint16_t loop_id, uint16_t priority, if (rval != QLA_SUCCESS) { ql_dbg(ql_dbg_mbx, vha, 0x10cd, "Failed=%x.\n", rval); } else { - ql_dbg(ql_dbg_mbx, vha, 0x10cc, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10cc, + "Done %s.\n", __func__); } return rval; @@ -4196,7 +4424,8 @@ qla2x00_get_thermal_temp(scsi_qla_host_t *vha, uint16_t *temp, uint16_t *frac) uint8_t byte; struct qla_hw_data *ha = vha->hw; - ql_dbg(ql_dbg_mbx, vha, 0x10ca, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10ca, + "Entered %s.\n", __func__); /* Integer part */ rval = qla2x00_read_sfp(vha, 0, &byte, 0x98, 0x01, 1, BIT_13|BIT_0); @@ -4216,7 +4445,8 @@ qla2x00_get_thermal_temp(scsi_qla_host_t *vha, uint16_t *temp, uint16_t *frac) } *frac = (byte >> 6) * 25; - ql_dbg(ql_dbg_mbx, vha, 0x1018, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1018, + "Done %s.\n", __func__); fail: return rval; } @@ -4229,7 +4459,8 @@ qla82xx_mbx_intr_enable(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x1017, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1017, + "Entered %s.\n", __func__); if (!IS_FWI2_CAPABLE(ha)) return QLA_FUNCTION_FAILED; @@ -4248,7 +4479,8 @@ qla82xx_mbx_intr_enable(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx, vha, 0x1016, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x100e, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x100e, + "Done %s.\n", __func__); } return rval; @@ -4262,7 +4494,8 @@ qla82xx_mbx_intr_disable(scsi_qla_host_t *vha) mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - ql_dbg(ql_dbg_mbx, vha, 0x100d, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x100d, + "Entered %s.\n", __func__); if (!IS_QLA82XX(ha)) return QLA_FUNCTION_FAILED; @@ -4281,7 +4514,8 @@ qla82xx_mbx_intr_disable(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx, vha, 0x100c, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x100b, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x100b, + "Done %s.\n", __func__); } return rval; @@ -4295,7 +4529,8 @@ qla82xx_md_get_template_size(scsi_qla_host_t *vha) mbx_cmd_t *mcp = &mc; int rval = QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x111f, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x111f, + "Entered %s.\n", __func__); memset(mcp->mb, 0 , sizeof(mcp->mb)); mcp->mb[0] = LSW(MBC_DIAGNOSTIC_MINIDUMP_TEMPLATE); @@ -4318,7 +4553,8 @@ qla82xx_md_get_template_size(scsi_qla_host_t *vha) (mcp->mb[1] << 16) | mcp->mb[0], (mcp->mb[3] << 16) | mcp->mb[2]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1121, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1121, + "Done %s.\n", __func__); ha->md_template_size = ((mcp->mb[3] << 16) | mcp->mb[2]); if (!ha->md_template_size) { ql_dbg(ql_dbg_mbx, vha, 0x1122, @@ -4337,7 +4573,8 @@ qla82xx_md_get_template(scsi_qla_host_t *vha) mbx_cmd_t *mcp = &mc; int rval = QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x1123, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1123, + "Entered %s.\n", __func__); ha->md_tmplt_hdr = dma_alloc_coherent(&ha->pdev->dev, ha->md_template_size, &ha->md_tmplt_hdr_dma, GFP_KERNEL); @@ -4372,7 +4609,8 @@ qla82xx_md_get_template(scsi_qla_host_t *vha) ((mcp->mb[1] << 16) | mcp->mb[0]), ((mcp->mb[3] << 16) | mcp->mb[2])); } else - ql_dbg(ql_dbg_mbx, vha, 0x1126, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1126, + "Done %s.\n", __func__); return rval; } @@ -4387,7 +4625,8 @@ qla81xx_set_led_config(scsi_qla_host_t *vha, uint16_t *led_cfg) if (!IS_QLA81XX(ha) && !IS_QLA8031(ha)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x1133, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1133, + "Entered %s.\n", __func__); memset(mcp, 0, sizeof(mbx_cmd_t)); mcp->mb[0] = MBC_SET_LED_CONFIG; @@ -4412,7 +4651,8 @@ qla81xx_set_led_config(scsi_qla_host_t *vha, uint16_t *led_cfg) ql_dbg(ql_dbg_mbx, vha, 0x1134, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1135, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1135, + "Done %s.\n", __func__); } return rval; @@ -4429,7 +4669,8 @@ qla81xx_get_led_config(scsi_qla_host_t *vha, uint16_t *led_cfg) if (!IS_QLA81XX(ha) && !IS_QLA8031(ha)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x1136, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1136, + "Entered %s.\n", __func__); memset(mcp, 0, sizeof(mbx_cmd_t)); mcp->mb[0] = MBC_GET_LED_CONFIG; @@ -4454,7 +4695,8 @@ qla81xx_get_led_config(scsi_qla_host_t *vha, uint16_t *led_cfg) led_cfg[4] = mcp->mb[5]; led_cfg[5] = mcp->mb[6]; } - ql_dbg(ql_dbg_mbx, vha, 0x1138, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1138, + "Done %s.\n", __func__); } return rval; @@ -4471,7 +4713,7 @@ qla82xx_mbx_beacon_ctl(scsi_qla_host_t *vha, int enable) if (!IS_QLA82XX(ha)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x1127, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1127, "Entered %s.\n", __func__); memset(mcp, 0, sizeof(mbx_cmd_t)); @@ -4491,7 +4733,7 @@ qla82xx_mbx_beacon_ctl(scsi_qla_host_t *vha, int enable) ql_dbg(ql_dbg_mbx, vha, 0x1128, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1129, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1129, "Done %s.\n", __func__); } @@ -4509,7 +4751,8 @@ qla83xx_write_remote_reg(scsi_qla_host_t *vha, uint32_t reg, uint32_t data) if (!IS_QLA83XX(ha)) return QLA_FUNCTION_FAILED; - ql_dbg(ql_dbg_mbx, vha, 0x1130, "Entered %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1130, + "Entered %s.\n", __func__); mcp->mb[0] = MBC_WRITE_REMOTE_REG; mcp->mb[1] = LSW(reg); @@ -4527,7 +4770,7 @@ qla83xx_write_remote_reg(scsi_qla_host_t *vha, uint32_t reg, uint32_t data) ql_dbg(ql_dbg_mbx, vha, 0x1131, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); } else { - ql_dbg(ql_dbg_mbx, vha, 0x1132, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1132, "Done %s.\n", __func__); } @@ -4543,13 +4786,14 @@ qla2x00_port_logout(scsi_qla_host_t *vha, struct fc_port *fcport) mbx_cmd_t *mcp = &mc; if (IS_QLA2100(ha) || IS_QLA2200(ha)) { - ql_dbg(ql_dbg_mbx, vha, 0x113b, + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x113b, "Implicit LOGO Unsupported.\n"); return QLA_FUNCTION_FAILED; } - ql_dbg(ql_dbg_mbx, vha, 0x113c, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x113c, + "Entering %s.\n", __func__); /* Perform Implicit LOGO. */ mcp->mb[0] = MBC_PORT_LOGOUT; @@ -4564,7 +4808,8 @@ qla2x00_port_logout(scsi_qla_host_t *vha, struct fc_port *fcport) ql_dbg(ql_dbg_mbx, vha, 0x113d, "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); else - ql_dbg(ql_dbg_mbx, vha, 0x113e, "Done %s.\n", __func__); + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x113e, + "Done %s.\n", __func__); return rval; } diff --git a/drivers/scsi/qla2xxx/qla_mid.c b/drivers/scsi/qla2xxx/qla_mid.c index aa062a1b0ca..3e8b32419e6 100644 --- a/drivers/scsi/qla2xxx/qla_mid.c +++ b/drivers/scsi/qla2xxx/qla_mid.c @@ -6,6 +6,7 @@ */ #include "qla_def.h" #include "qla_gbl.h" +#include "qla_target.h" #include <linux/moduleparam.h> #include <linux/vmalloc.h> @@ -49,6 +50,9 @@ qla24xx_allocate_vp_id(scsi_qla_host_t *vha) spin_lock_irqsave(&ha->vport_slock, flags); list_add_tail(&vha->list, &ha->vp_list); + + qlt_update_vp_map(vha, SET_VP_IDX); + spin_unlock_irqrestore(&ha->vport_slock, flags); mutex_unlock(&ha->vport_lock); @@ -79,6 +83,7 @@ qla24xx_deallocate_vp_id(scsi_qla_host_t *vha) spin_lock_irqsave(&ha->vport_slock, flags); } list_del(&vha->list); + qlt_update_vp_map(vha, RESET_VP_IDX); spin_unlock_irqrestore(&ha->vport_slock, flags); vp_id = vha->vp_idx; @@ -134,7 +139,7 @@ qla2x00_mark_vp_devices_dead(scsi_qla_host_t *vha) list_for_each_entry(fcport, &vha->vp_fcports, list) { ql_dbg(ql_dbg_vport, vha, 0xa001, "Marking port dead, loop_id=0x%04x : %x.\n", - fcport->loop_id, fcport->vp_idx); + fcport->loop_id, fcport->vha->vp_idx); qla2x00_mark_device_lost(vha, fcport, 0, 0); qla2x00_set_fcport_state(fcport, FCS_UNCONFIGURED); @@ -150,6 +155,9 @@ qla24xx_disable_vp(scsi_qla_host_t *vha) atomic_set(&vha->loop_state, LOOP_DOWN); atomic_set(&vha->loop_down_timer, LOOP_DOWN_TIME); + /* Remove port id from vp target map */ + qlt_update_vp_map(vha, RESET_AL_PA); + qla2x00_mark_vp_devices_dead(vha); atomic_set(&vha->vp_state, VP_FAILED); vha->flags.management_server_logged_in = 0; @@ -295,10 +303,8 @@ qla2x00_vp_abort_isp(scsi_qla_host_t *vha) static int qla2x00_do_dpc_vp(scsi_qla_host_t *vha) { - ql_dbg(ql_dbg_dpc, vha, 0x4012, - "Entering %s.\n", __func__); - ql_dbg(ql_dbg_dpc, vha, 0x4013, - "vp_flags: 0x%lx.\n", vha->vp_flags); + ql_dbg(ql_dbg_dpc + ql_dbg_verbose, vha, 0x4012, + "Entering %s vp_flags: 0x%lx.\n", __func__, vha->vp_flags); qla2x00_do_work(vha); @@ -348,7 +354,7 @@ qla2x00_do_dpc_vp(scsi_qla_host_t *vha) } } - ql_dbg(ql_dbg_dpc, vha, 0x401c, + ql_dbg(ql_dbg_dpc + ql_dbg_verbose, vha, 0x401c, "Exiting %s.\n", __func__); return 0; } diff --git a/drivers/scsi/qla2xxx/qla_nx.c b/drivers/scsi/qla2xxx/qla_nx.c index de722a93343..caf627ba7fa 100644 --- a/drivers/scsi/qla2xxx/qla_nx.c +++ b/drivers/scsi/qla2xxx/qla_nx.c @@ -1190,12 +1190,12 @@ qla82xx_pinit_from_rom(scsi_qla_host_t *vha) } /* Offset in flash = lower 16 bits - * Number of enteries = upper 16 bits + * Number of entries = upper 16 bits */ offset = n & 0xffffU; n = (n >> 16) & 0xffffU; - /* number of addr/value pair should not exceed 1024 enteries */ + /* number of addr/value pair should not exceed 1024 entries */ if (n >= 1024) { ql_log(ql_log_fatal, vha, 0x0071, "Card flash not initialized:n=0x%x.\n", n); @@ -2050,7 +2050,7 @@ qla82xx_intr_handler(int irq, void *dev_id) rsp = (struct rsp_que *) dev_id; if (!rsp) { - ql_log(ql_log_info, NULL, 0xb054, + ql_log(ql_log_info, NULL, 0xb053, "%s: NULL response queue pointer.\n", __func__); return IRQ_NONE; } @@ -2446,7 +2446,7 @@ qla82xx_load_fw(scsi_qla_host_t *vha) if (qla82xx_fw_load_from_flash(ha) == QLA_SUCCESS) { ql_log(ql_log_info, vha, 0x00a1, - "Firmware loaded successully from flash.\n"); + "Firmware loaded successfully from flash.\n"); return QLA_SUCCESS; } else { ql_log(ql_log_warn, vha, 0x0108, @@ -2461,7 +2461,7 @@ try_blob_fw: blob = ha->hablob = qla2x00_request_firmware(vha); if (!blob) { ql_log(ql_log_fatal, vha, 0x00a3, - "Firmware image not preset.\n"); + "Firmware image not present.\n"); goto fw_load_failed; } @@ -2689,7 +2689,7 @@ qla82xx_write_flash_data(struct scsi_qla_host *vha, uint32_t *dwptr, if (!optrom) { ql_log(ql_log_warn, vha, 0xb01b, "Unable to allocate memory " - "for optron burst write (%x KB).\n", + "for optrom burst write (%x KB).\n", OPTROM_BURST_SIZE / 1024); } } @@ -2960,9 +2960,8 @@ qla82xx_need_qsnt_handler(scsi_qla_host_t *vha) * changing the state to DEV_READY */ ql_log(ql_log_info, vha, 0xb023, - "%s : QUIESCENT TIMEOUT.\n", QLA2XXX_DRIVER_NAME); - ql_log(ql_log_info, vha, 0xb024, - "DRV_ACTIVE:%d DRV_STATE:%d.\n", + "%s : QUIESCENT TIMEOUT DRV_ACTIVE:%d " + "DRV_STATE:%d.\n", QLA2XXX_DRIVER_NAME, drv_active, drv_state); qla82xx_wr_32(ha, QLA82XX_CRB_DEV_STATE, QLA82XX_DEV_READY); @@ -3129,7 +3128,7 @@ qla82xx_need_reset_handler(scsi_qla_host_t *vha) if (ql2xmdenable) { if (qla82xx_md_collect(vha)) ql_log(ql_log_warn, vha, 0xb02c, - "Not able to collect minidump.\n"); + "Minidump not collected.\n"); } else ql_log(ql_log_warn, vha, 0xb04f, "Minidump disabled.\n"); @@ -3160,11 +3159,11 @@ qla82xx_check_md_needed(scsi_qla_host_t *vha) "Firmware version differs " "Previous version: %d:%d:%d - " "New version: %d:%d:%d\n", + fw_major_version, fw_minor_version, + fw_subminor_version, ha->fw_major_version, ha->fw_minor_version, - ha->fw_subminor_version, - fw_major_version, fw_minor_version, - fw_subminor_version); + ha->fw_subminor_version); /* Release MiniDump resources */ qla82xx_md_free(vha); /* ALlocate MiniDump resources */ @@ -3325,6 +3324,30 @@ exit: return rval; } +static int qla82xx_check_temp(scsi_qla_host_t *vha) +{ + uint32_t temp, temp_state, temp_val; + struct qla_hw_data *ha = vha->hw; + + temp = qla82xx_rd_32(ha, CRB_TEMP_STATE); + temp_state = qla82xx_get_temp_state(temp); + temp_val = qla82xx_get_temp_val(temp); + + if (temp_state == QLA82XX_TEMP_PANIC) { + ql_log(ql_log_warn, vha, 0x600e, + "Device temperature %d degrees C exceeds " + " maximum allowed. Hardware has been shut down.\n", + temp_val); + return 1; + } else if (temp_state == QLA82XX_TEMP_WARN) { + ql_log(ql_log_warn, vha, 0x600f, + "Device temperature %d degrees C exceeds " + "operating range. Immediate action needed.\n", + temp_val); + } + return 0; +} + void qla82xx_clear_pending_mbx(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; @@ -3347,18 +3370,20 @@ void qla82xx_watchdog(scsi_qla_host_t *vha) /* don't poll if reset is going on */ if (!ha->flags.isp82xx_reset_hdlr_active) { dev_state = qla82xx_rd_32(ha, QLA82XX_CRB_DEV_STATE); - if (dev_state == QLA82XX_DEV_NEED_RESET && + if (qla82xx_check_temp(vha)) { + set_bit(ISP_UNRECOVERABLE, &vha->dpc_flags); + ha->flags.isp82xx_fw_hung = 1; + qla82xx_clear_pending_mbx(vha); + } else if (dev_state == QLA82XX_DEV_NEED_RESET && !test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags)) { ql_log(ql_log_warn, vha, 0x6001, "Adapter reset needed.\n"); set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); - qla2xxx_wake_dpc(vha); } else if (dev_state == QLA82XX_DEV_NEED_QUIESCENT && !test_bit(ISP_QUIESCE_NEEDED, &vha->dpc_flags)) { ql_log(ql_log_warn, vha, 0x6002, "Quiescent needed.\n"); set_bit(ISP_QUIESCE_NEEDED, &vha->dpc_flags); - qla2xxx_wake_dpc(vha); } else { if (qla82xx_check_fw_alive(vha)) { ql_dbg(ql_dbg_timer, vha, 0x6011, @@ -3398,7 +3423,6 @@ void qla82xx_watchdog(scsi_qla_host_t *vha) set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); } - qla2xxx_wake_dpc(vha); ha->flags.isp82xx_fw_hung = 1; ql_log(ql_log_warn, vha, 0x6007, "Firmware hung.\n"); qla82xx_clear_pending_mbx(vha); @@ -4113,6 +4137,14 @@ qla82xx_md_collect(scsi_qla_host_t *vha) goto md_failed; } + if (ha->flags.isp82xx_no_md_cap) { + ql_log(ql_log_warn, vha, 0xb054, + "Forced reset from application, " + "ignore minidump capture\n"); + ha->flags.isp82xx_no_md_cap = 0; + goto md_failed; + } + if (qla82xx_validate_template_chksum(vha)) { ql_log(ql_log_info, vha, 0xb039, "Template checksum validation error\n"); diff --git a/drivers/scsi/qla2xxx/qla_nx.h b/drivers/scsi/qla2xxx/qla_nx.h index 4ac50e27466..6eb210e3cc6 100644 --- a/drivers/scsi/qla2xxx/qla_nx.h +++ b/drivers/scsi/qla2xxx/qla_nx.h @@ -26,6 +26,7 @@ #define CRB_RCVPEG_STATE QLA82XX_REG(0x13c) #define BOOT_LOADER_DIMM_STATUS QLA82XX_REG(0x54) #define CRB_DMA_SHIFT QLA82XX_REG(0xcc) +#define CRB_TEMP_STATE QLA82XX_REG(0x1b4) #define QLA82XX_DMA_SHIFT_VALUE 0x55555555 #define QLA82XX_HW_H0_CH_HUB_ADR 0x05 @@ -561,7 +562,6 @@ #define QLA82XX_FW_VERSION_SUB (QLA82XX_CAM_RAM(0x158)) #define QLA82XX_PCIE_REG(reg) (QLA82XX_CRB_PCIE + (reg)) -#define PCIE_CHICKEN3 (0x120c8) #define PCIE_SETUP_FUNCTION (0x12040) #define PCIE_SETUP_FUNCTION2 (0x12048) @@ -1178,4 +1178,16 @@ static const int MD_MIU_TEST_AGT_RDDATA[] = { 0x410000A8, 0x410000AC, #define CRB_NIU_XG_PAUSE_CTL_P0 0x1 #define CRB_NIU_XG_PAUSE_CTL_P1 0x8 +#define qla82xx_get_temp_val(x) ((x) >> 16) +#define qla82xx_get_temp_state(x) ((x) & 0xffff) +#define qla82xx_encode_temp(val, state) (((val) << 16) | (state)) + +/* + * Temperature control. + */ +enum { + QLA82XX_TEMP_NORMAL = 0x1, /* Normal operating range */ + QLA82XX_TEMP_WARN, /* Sound alert, temperature getting high */ + QLA82XX_TEMP_PANIC /* Fatal error, hardware has shut down. */ +}; #endif diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index c9c56a8427f..6d1d873a20e 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -13,12 +13,13 @@ #include <linux/mutex.h> #include <linux/kobject.h> #include <linux/slab.h> - #include <scsi/scsi_tcq.h> #include <scsi/scsicam.h> #include <scsi/scsi_transport.h> #include <scsi/scsi_transport_fc.h> +#include "qla_target.h" + /* * Driver version */ @@ -40,6 +41,12 @@ static struct kmem_cache *ctx_cachep; */ int ql_errlev = ql_log_all; +int ql2xenableclass2; +module_param(ql2xenableclass2, int, S_IRUGO|S_IRUSR); +MODULE_PARM_DESC(ql2xenableclass2, + "Specify if Class 2 operations are supported from the very " + "beginning. Default is 0 - class 2 not supported."); + int ql2xlogintimeout = 20; module_param(ql2xlogintimeout, int, S_IRUGO); MODULE_PARM_DESC(ql2xlogintimeout, @@ -255,6 +262,8 @@ struct scsi_host_template qla2xxx_driver_template = { .max_sectors = 0xFFFF, .shost_attrs = qla2x00_host_attrs, + + .supported_mode = MODE_INITIATOR, }; static struct scsi_transport_template *qla2xxx_transport_template = NULL; @@ -306,7 +315,8 @@ static void qla2x00_free_fw_dump(struct qla_hw_data *); static void qla2x00_mem_free(struct qla_hw_data *); /* -------------------------------------------------------------------------- */ -static int qla2x00_alloc_queues(struct qla_hw_data *ha) +static int qla2x00_alloc_queues(struct qla_hw_data *ha, struct req_que *req, + struct rsp_que *rsp) { scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); ha->req_q_map = kzalloc(sizeof(struct req_que *) * ha->max_req_queues, @@ -324,6 +334,12 @@ static int qla2x00_alloc_queues(struct qla_hw_data *ha) "Unable to allocate memory for response queue ptrs.\n"); goto fail_rsp_map; } + /* + * Make sure we record at least the request and response queue zero in + * case we need to free them if part of the probe fails. + */ + ha->rsp_q_map[0] = rsp; + ha->req_q_map[0] = req; set_bit(0, ha->rsp_qid_map); set_bit(0, ha->req_qid_map); return 1; @@ -642,12 +658,12 @@ qla2xxx_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) if (ha->flags.eeh_busy) { if (ha->flags.pci_channel_io_perm_failure) { - ql_dbg(ql_dbg_io, vha, 0x3001, + ql_dbg(ql_dbg_aer, vha, 0x9010, "PCI Channel IO permanent failure, exiting " "cmd=%p.\n", cmd); cmd->result = DID_NO_CONNECT << 16; } else { - ql_dbg(ql_dbg_io, vha, 0x3002, + ql_dbg(ql_dbg_aer, vha, 0x9011, "EEH_Busy, Requeuing the cmd=%p.\n", cmd); cmd->result = DID_REQUEUE << 16; } @@ -657,7 +673,7 @@ qla2xxx_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) rval = fc_remote_port_chkready(rport); if (rval) { cmd->result = rval; - ql_dbg(ql_dbg_io, vha, 0x3003, + ql_dbg(ql_dbg_io + ql_dbg_verbose, vha, 0x3003, "fc_remote_port_chkready failed for cmd=%p, rval=0x%x.\n", cmd, rval); goto qc24_fail_command; @@ -1136,7 +1152,7 @@ qla2xxx_eh_bus_reset(struct scsi_cmnd *cmd) ret = FAILED; ql_log(ql_log_info, vha, 0x8012, - "BUS RESET ISSUED nexus=%ld:%d%d.\n", vha->host_no, id, lun); + "BUS RESET ISSUED nexus=%ld:%d:%d.\n", vha->host_no, id, lun); if (qla2x00_wait_for_hba_online(vha) != QLA_SUCCESS) { ql_log(ql_log_fatal, vha, 0x8013, @@ -2180,6 +2196,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ql_dbg_pci(ql_dbg_init, pdev, 0x000a, "Memory allocated for ha=%p.\n", ha); ha->pdev = pdev; + ha->tgt.enable_class_2 = ql2xenableclass2; /* Clear our data area */ ha->bars = bars; @@ -2243,6 +2260,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->mbx_count = MAILBOX_REGISTER_COUNT; req_length = REQUEST_ENTRY_CNT_24XX; rsp_length = RESPONSE_ENTRY_CNT_2300; + ha->tgt.atio_q_length = ATIO_ENTRY_CNT_24XX; ha->max_loop_id = SNS_LAST_LOOP_ID_2300; ha->init_cb_size = sizeof(struct mid_init_cb_24xx); ha->gid_list_info_size = 8; @@ -2258,6 +2276,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->mbx_count = MAILBOX_REGISTER_COUNT; req_length = REQUEST_ENTRY_CNT_24XX; rsp_length = RESPONSE_ENTRY_CNT_2300; + ha->tgt.atio_q_length = ATIO_ENTRY_CNT_24XX; ha->max_loop_id = SNS_LAST_LOOP_ID_2300; ha->init_cb_size = sizeof(struct mid_init_cb_24xx); ha->gid_list_info_size = 8; @@ -2417,6 +2436,17 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) host->max_cmd_len, host->max_channel, host->max_lun, host->transportt, sht->vendor_id); +que_init: + /* Alloc arrays of request and response ring ptrs */ + if (!qla2x00_alloc_queues(ha, req, rsp)) { + ql_log(ql_log_fatal, base_vha, 0x003d, + "Failed to allocate memory for queue pointers..." + "aborting.\n"); + goto probe_init_failed; + } + + qlt_probe_one_stage1(base_vha, ha); + /* Set up the irqs */ ret = qla2x00_request_irqs(ha, rsp); if (ret) @@ -2424,20 +2454,10 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) pci_save_state(pdev); - /* Alloc arrays of request and response ring ptrs */ -que_init: - if (!qla2x00_alloc_queues(ha)) { - ql_log(ql_log_fatal, base_vha, 0x003d, - "Failed to allocate memory for queue pointers.. aborting.\n"); - goto probe_init_failed; - } - - ha->rsp_q_map[0] = rsp; - ha->req_q_map[0] = req; + /* Assign back pointers */ rsp->req = req; req->rsp = rsp; - set_bit(0, ha->req_qid_map); - set_bit(0, ha->rsp_qid_map); + /* FWI2-capable only. */ req->req_q_in = &ha->iobase->isp24.req_q_in; req->req_q_out = &ha->iobase->isp24.req_q_out; @@ -2514,6 +2534,14 @@ que_init: ql_dbg(ql_dbg_init, base_vha, 0x00ee, "DPC thread started successfully.\n"); + /* + * If we're not coming up in initiator mode, we might sit for + * a while without waking up the dpc thread, which leads to a + * stuck process warning. So just kick the dpc once here and + * let the kthread start (and go back to sleep in qla2x00_do_dpc). + */ + qla2xxx_wake_dpc(base_vha); + skip_dpc: list_add_tail(&base_vha->list, &ha->vp_list); base_vha->host->irq = ha->pdev->irq; @@ -2559,7 +2587,11 @@ skip_dpc: ql_dbg(ql_dbg_init, base_vha, 0x00f2, "Init done and hba is online.\n"); - scsi_scan_host(host); + if (qla_ini_mode_enabled(base_vha)) + scsi_scan_host(host); + else + ql_dbg(ql_dbg_init, base_vha, 0x0122, + "skipping scsi_scan_host() for non-initiator port\n"); qla2x00_alloc_sysfs_attr(base_vha); @@ -2577,11 +2609,17 @@ skip_dpc: base_vha->host_no, ha->isp_ops->fw_version_str(base_vha, fw_str)); + qlt_add_target(ha, base_vha); + return 0; probe_init_failed: qla2x00_free_req_que(ha, req); + ha->req_q_map[0] = NULL; + clear_bit(0, ha->req_qid_map); qla2x00_free_rsp_que(ha, rsp); + ha->rsp_q_map[0] = NULL; + clear_bit(0, ha->rsp_qid_map); ha->max_req_queues = ha->max_rsp_queues = 0; probe_failed: @@ -2621,6 +2659,22 @@ probe_out: } static void +qla2x00_stop_dpc_thread(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct task_struct *t = ha->dpc_thread; + + if (ha->dpc_thread == NULL) + return; + /* + * qla2xxx_wake_dpc checks for ->dpc_thread + * so we need to zero it out. + */ + ha->dpc_thread = NULL; + kthread_stop(t); +} + +static void qla2x00_shutdown(struct pci_dev *pdev) { scsi_qla_host_t *vha; @@ -2663,9 +2717,18 @@ qla2x00_remove_one(struct pci_dev *pdev) struct qla_hw_data *ha; unsigned long flags; + /* + * If the PCI device is disabled that means that probe failed and any + * resources should be have cleaned up on probe exit. + */ + if (!atomic_read(&pdev->enable_cnt)) + return; + base_vha = pci_get_drvdata(pdev); ha = base_vha->hw; + ha->flags.host_shutting_down = 1; + mutex_lock(&ha->vport_lock); while (ha->cur_vport_count) { struct Scsi_Host *scsi_host; @@ -2719,6 +2782,7 @@ qla2x00_remove_one(struct pci_dev *pdev) ha->dpc_thread = NULL; kthread_stop(t); } + qlt_remove_target(ha, base_vha); qla2x00_free_sysfs_attr(base_vha); @@ -2770,17 +2834,7 @@ qla2x00_free_device(scsi_qla_host_t *vha) if (vha->timer_active) qla2x00_stop_timer(vha); - /* Kill the kernel thread for this host */ - if (ha->dpc_thread) { - struct task_struct *t = ha->dpc_thread; - - /* - * qla2xxx_wake_dpc checks for ->dpc_thread - * so we need to zero it out. - */ - ha->dpc_thread = NULL; - kthread_stop(t); - } + qla2x00_stop_dpc_thread(vha); qla25xx_delete_queues(vha); @@ -2842,8 +2896,10 @@ qla2x00_schedule_rport_del(struct scsi_qla_host *vha, fc_port_t *fcport, spin_unlock_irqrestore(vha->host->host_lock, flags); set_bit(FCPORT_UPDATE_NEEDED, &base_vha->dpc_flags); qla2xxx_wake_dpc(base_vha); - } else + } else { fc_remote_port_delete(rport); + qlt_fc_port_deleted(vha, fcport); + } } /* @@ -2859,7 +2915,7 @@ void qla2x00_mark_device_lost(scsi_qla_host_t *vha, fc_port_t *fcport, int do_login, int defer) { if (atomic_read(&fcport->state) == FCS_ONLINE && - vha->vp_idx == fcport->vp_idx) { + vha->vp_idx == fcport->vha->vp_idx) { qla2x00_set_fcport_state(fcport, FCS_DEVICE_LOST); qla2x00_schedule_rport_del(vha, fcport, defer); } @@ -2908,7 +2964,7 @@ qla2x00_mark_all_devices_lost(scsi_qla_host_t *vha, int defer) fc_port_t *fcport; list_for_each_entry(fcport, &vha->vp_fcports, list) { - if (vha->vp_idx != 0 && vha->vp_idx != fcport->vp_idx) + if (vha->vp_idx != 0 && vha->vp_idx != fcport->vha->vp_idx) continue; /* @@ -2921,7 +2977,7 @@ qla2x00_mark_all_devices_lost(scsi_qla_host_t *vha, int defer) qla2x00_set_fcport_state(fcport, FCS_DEVICE_LOST); if (defer) qla2x00_schedule_rport_del(vha, fcport, defer); - else if (vha->vp_idx == fcport->vp_idx) + else if (vha->vp_idx == fcport->vha->vp_idx) qla2x00_schedule_rport_del(vha, fcport, defer); } } @@ -2946,10 +3002,13 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len, if (!ha->init_cb) goto fail; + if (qlt_mem_alloc(ha) < 0) + goto fail_free_init_cb; + ha->gid_list = dma_alloc_coherent(&ha->pdev->dev, qla2x00_gid_list_size(ha), &ha->gid_list_dma, GFP_KERNEL); if (!ha->gid_list) - goto fail_free_init_cb; + goto fail_free_tgt_mem; ha->srb_mempool = mempool_create_slab_pool(SRB_MIN_REQ, srb_cachep); if (!ha->srb_mempool) @@ -3167,6 +3226,8 @@ fail_free_gid_list: ha->gid_list_dma); ha->gid_list = NULL; ha->gid_list_dma = 0; +fail_free_tgt_mem: + qlt_mem_free(ha); fail_free_init_cb: dma_free_coherent(&ha->pdev->dev, ha->init_cb_size, ha->init_cb, ha->init_cb_dma); @@ -3282,6 +3343,8 @@ qla2x00_mem_free(struct qla_hw_data *ha) if (ha->ctx_mempool) mempool_destroy(ha->ctx_mempool); + qlt_mem_free(ha); + if (ha->init_cb) dma_free_coherent(&ha->pdev->dev, ha->init_cb_size, ha->init_cb, ha->init_cb_dma); @@ -3311,6 +3374,10 @@ qla2x00_mem_free(struct qla_hw_data *ha) ha->gid_list = NULL; ha->gid_list_dma = 0; + + ha->tgt.atio_ring = NULL; + ha->tgt.atio_dma = 0; + ha->tgt.tgt_vp_map = NULL; } struct scsi_qla_host *qla2x00_create_host(struct scsi_host_template *sht, @@ -3671,10 +3738,9 @@ qla2x00_do_dpc(void *data) ha->dpc_active = 1; - ql_dbg(ql_dbg_dpc, base_vha, 0x4001, - "DPC handler waking up.\n"); - ql_dbg(ql_dbg_dpc, base_vha, 0x4002, - "dpc_flags=0x%lx.\n", base_vha->dpc_flags); + ql_dbg(ql_dbg_dpc + ql_dbg_verbose, base_vha, 0x4001, + "DPC handler waking up, dpc_flags=0x%lx.\n", + base_vha->dpc_flags); qla2x00_do_work(base_vha); @@ -3740,6 +3806,16 @@ qla2x00_do_dpc(void *data) clear_bit(FCPORT_UPDATE_NEEDED, &base_vha->dpc_flags); } + if (test_bit(SCR_PENDING, &base_vha->dpc_flags)) { + int ret; + ret = qla2x00_send_change_request(base_vha, 0x3, 0); + if (ret != QLA_SUCCESS) + ql_log(ql_log_warn, base_vha, 0x121, + "Failed to enable receiving of RSCN " + "requests: 0x%x.\n", ret); + clear_bit(SCR_PENDING, &base_vha->dpc_flags); + } + if (test_bit(ISP_QUIESCE_NEEDED, &base_vha->dpc_flags)) { ql_dbg(ql_dbg_dpc, base_vha, 0x4009, "Quiescence mode scheduled.\n"); @@ -4457,6 +4533,21 @@ qla2x00_module_init(void) return -ENOMEM; } + /* Initialize target kmem_cache and mem_pools */ + ret = qlt_init(); + if (ret < 0) { + kmem_cache_destroy(srb_cachep); + return ret; + } else if (ret > 0) { + /* + * If initiator mode is explictly disabled by qlt_init(), + * prevent scsi_transport_fc.c:fc_scsi_scan_rport() from + * performing scsi_scan_target() during LOOP UP event. + */ + qla2xxx_transport_functions.disable_target_scan = 1; + qla2xxx_transport_vport_functions.disable_target_scan = 1; + } + /* Derive version string. */ strcpy(qla2x00_version_str, QLA2XXX_VERSION); if (ql2xextended_error_logging) @@ -4468,6 +4559,7 @@ qla2x00_module_init(void) kmem_cache_destroy(srb_cachep); ql_log(ql_log_fatal, NULL, 0x0002, "fc_attach_transport failed...Failing load!.\n"); + qlt_exit(); return -ENODEV; } @@ -4481,6 +4573,7 @@ qla2x00_module_init(void) fc_attach_transport(&qla2xxx_transport_vport_functions); if (!qla2xxx_transport_vport_template) { kmem_cache_destroy(srb_cachep); + qlt_exit(); fc_release_transport(qla2xxx_transport_template); ql_log(ql_log_fatal, NULL, 0x0004, "fc_attach_transport vport failed...Failing load!.\n"); @@ -4492,6 +4585,7 @@ qla2x00_module_init(void) ret = pci_register_driver(&qla2xxx_pci_driver); if (ret) { kmem_cache_destroy(srb_cachep); + qlt_exit(); fc_release_transport(qla2xxx_transport_template); fc_release_transport(qla2xxx_transport_vport_template); ql_log(ql_log_fatal, NULL, 0x0006, @@ -4511,6 +4605,7 @@ qla2x00_module_exit(void) pci_unregister_driver(&qla2xxx_pci_driver); qla2x00_release_firmware(); kmem_cache_destroy(srb_cachep); + qlt_exit(); if (ctx_cachep) kmem_cache_destroy(ctx_cachep); fc_release_transport(qla2xxx_transport_template); diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c new file mode 100644 index 00000000000..04f80ebf09e --- /dev/null +++ b/drivers/scsi/qla2xxx/qla_target.c @@ -0,0 +1,4973 @@ +/* + * qla_target.c SCSI LLD infrastructure for QLogic 22xx/23xx/24xx/25xx + * + * based on qla2x00t.c code: + * + * Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst@vlnb.net> + * Copyright (C) 2004 - 2005 Leonid Stoljar + * Copyright (C) 2006 Nathaniel Clark <nate@misrule.us> + * Copyright (C) 2006 - 2010 ID7 Ltd. + * + * Forward port and refactoring to modern qla2xxx and target/configfs + * + * Copyright (C) 2010-2011 Nicholas A. Bellinger <nab@kernel.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2 + * of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/blkdev.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_tcq.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> + +#include "qla_def.h" +#include "qla_target.h" + +static char *qlini_mode = QLA2XXX_INI_MODE_STR_ENABLED; +module_param(qlini_mode, charp, S_IRUGO); +MODULE_PARM_DESC(qlini_mode, + "Determines when initiator mode will be enabled. Possible values: " + "\"exclusive\" - initiator mode will be enabled on load, " + "disabled on enabling target mode and then on disabling target mode " + "enabled back; " + "\"disabled\" - initiator mode will never be enabled; " + "\"enabled\" (default) - initiator mode will always stay enabled."); + +static int ql2x_ini_mode = QLA2XXX_INI_MODE_EXCLUSIVE; + +/* + * From scsi/fc/fc_fcp.h + */ +enum fcp_resp_rsp_codes { + FCP_TMF_CMPL = 0, + FCP_DATA_LEN_INVALID = 1, + FCP_CMND_FIELDS_INVALID = 2, + FCP_DATA_PARAM_MISMATCH = 3, + FCP_TMF_REJECTED = 4, + FCP_TMF_FAILED = 5, + FCP_TMF_INVALID_LUN = 9, +}; + +/* + * fc_pri_ta from scsi/fc/fc_fcp.h + */ +#define FCP_PTA_SIMPLE 0 /* simple task attribute */ +#define FCP_PTA_HEADQ 1 /* head of queue task attribute */ +#define FCP_PTA_ORDERED 2 /* ordered task attribute */ +#define FCP_PTA_ACA 4 /* auto. contigent allegiance */ +#define FCP_PTA_MASK 7 /* mask for task attribute field */ +#define FCP_PRI_SHIFT 3 /* priority field starts in bit 3 */ +#define FCP_PRI_RESVD_MASK 0x80 /* reserved bits in priority field */ + +/* + * This driver calls qla2x00_alloc_iocbs() and qla2x00_issue_marker(), which + * must be called under HW lock and could unlock/lock it inside. + * It isn't an issue, since in the current implementation on the time when + * those functions are called: + * + * - Either context is IRQ and only IRQ handler can modify HW data, + * including rings related fields, + * + * - Or access to target mode variables from struct qla_tgt doesn't + * cross those functions boundaries, except tgt_stop, which + * additionally protected by irq_cmd_count. + */ +/* Predefs for callbacks handed to qla2xxx LLD */ +static void qlt_24xx_atio_pkt(struct scsi_qla_host *ha, + struct atio_from_isp *pkt); +static void qlt_response_pkt(struct scsi_qla_host *ha, response_t *pkt); +static int qlt_issue_task_mgmt(struct qla_tgt_sess *sess, uint32_t lun, + int fn, void *iocb, int flags); +static void qlt_send_term_exchange(struct scsi_qla_host *ha, struct qla_tgt_cmd + *cmd, struct atio_from_isp *atio, int ha_locked); +static void qlt_reject_free_srr_imm(struct scsi_qla_host *ha, + struct qla_tgt_srr_imm *imm, int ha_lock); +/* + * Global Variables + */ +static struct kmem_cache *qla_tgt_cmd_cachep; +static struct kmem_cache *qla_tgt_mgmt_cmd_cachep; +static mempool_t *qla_tgt_mgmt_cmd_mempool; +static struct workqueue_struct *qla_tgt_wq; +static DEFINE_MUTEX(qla_tgt_mutex); +static LIST_HEAD(qla_tgt_glist); + +/* ha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */ +static struct qla_tgt_sess *qlt_find_sess_by_port_name( + struct qla_tgt *tgt, + const uint8_t *port_name) +{ + struct qla_tgt_sess *sess; + + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { + if (!memcmp(sess->port_name, port_name, WWN_SIZE)) + return sess; + } + + return NULL; +} + +/* Might release hw lock, then reaquire!! */ +static inline int qlt_issue_marker(struct scsi_qla_host *vha, int vha_locked) +{ + /* Send marker if required */ + if (unlikely(vha->marker_needed != 0)) { + int rc = qla2x00_issue_marker(vha, vha_locked); + if (rc != QLA_SUCCESS) { + ql_dbg(ql_dbg_tgt, vha, 0xe03d, + "qla_target(%d): issue_marker() failed\n", + vha->vp_idx); + } + return rc; + } + return QLA_SUCCESS; +} + +static inline +struct scsi_qla_host *qlt_find_host_by_d_id(struct scsi_qla_host *vha, + uint8_t *d_id) +{ + struct qla_hw_data *ha = vha->hw; + uint8_t vp_idx; + + if ((vha->d_id.b.area != d_id[1]) || (vha->d_id.b.domain != d_id[0])) + return NULL; + + if (vha->d_id.b.al_pa == d_id[2]) + return vha; + + BUG_ON(ha->tgt.tgt_vp_map == NULL); + vp_idx = ha->tgt.tgt_vp_map[d_id[2]].idx; + if (likely(test_bit(vp_idx, ha->vp_idx_map))) + return ha->tgt.tgt_vp_map[vp_idx].vha; + + return NULL; +} + +static inline +struct scsi_qla_host *qlt_find_host_by_vp_idx(struct scsi_qla_host *vha, + uint16_t vp_idx) +{ + struct qla_hw_data *ha = vha->hw; + + if (vha->vp_idx == vp_idx) + return vha; + + BUG_ON(ha->tgt.tgt_vp_map == NULL); + if (likely(test_bit(vp_idx, ha->vp_idx_map))) + return ha->tgt.tgt_vp_map[vp_idx].vha; + + return NULL; +} + +void qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha, + struct atio_from_isp *atio) +{ + switch (atio->u.raw.entry_type) { + case ATIO_TYPE7: + { + struct scsi_qla_host *host = qlt_find_host_by_d_id(vha, + atio->u.isp24.fcp_hdr.d_id); + if (unlikely(NULL == host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe03e, + "qla_target(%d): Received ATIO_TYPE7 " + "with unknown d_id %x:%x:%x\n", vha->vp_idx, + atio->u.isp24.fcp_hdr.d_id[0], + atio->u.isp24.fcp_hdr.d_id[1], + atio->u.isp24.fcp_hdr.d_id[2]); + break; + } + qlt_24xx_atio_pkt(host, atio); + break; + } + + case IMMED_NOTIFY_TYPE: + { + struct scsi_qla_host *host = vha; + struct imm_ntfy_from_isp *entry = + (struct imm_ntfy_from_isp *)atio; + + if ((entry->u.isp24.vp_index != 0xFF) && + (entry->u.isp24.nport_handle != 0xFFFF)) { + host = qlt_find_host_by_vp_idx(vha, + entry->u.isp24.vp_index); + if (unlikely(!host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe03f, + "qla_target(%d): Received " + "ATIO (IMMED_NOTIFY_TYPE) " + "with unknown vp_index %d\n", + vha->vp_idx, entry->u.isp24.vp_index); + break; + } + } + qlt_24xx_atio_pkt(host, atio); + break; + } + + default: + ql_dbg(ql_dbg_tgt, vha, 0xe040, + "qla_target(%d): Received unknown ATIO atio " + "type %x\n", vha->vp_idx, atio->u.raw.entry_type); + break; + } + + return; +} + +void qlt_response_pkt_all_vps(struct scsi_qla_host *vha, response_t *pkt) +{ + switch (pkt->entry_type) { + case CTIO_TYPE7: + { + struct ctio7_from_24xx *entry = (struct ctio7_from_24xx *)pkt; + struct scsi_qla_host *host = qlt_find_host_by_vp_idx(vha, + entry->vp_index); + if (unlikely(!host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe041, + "qla_target(%d): Response pkt (CTIO_TYPE7) " + "received, with unknown vp_index %d\n", + vha->vp_idx, entry->vp_index); + break; + } + qlt_response_pkt(host, pkt); + break; + } + + case IMMED_NOTIFY_TYPE: + { + struct scsi_qla_host *host = vha; + struct imm_ntfy_from_isp *entry = + (struct imm_ntfy_from_isp *)pkt; + + host = qlt_find_host_by_vp_idx(vha, entry->u.isp24.vp_index); + if (unlikely(!host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe042, + "qla_target(%d): Response pkt (IMMED_NOTIFY_TYPE) " + "received, with unknown vp_index %d\n", + vha->vp_idx, entry->u.isp24.vp_index); + break; + } + qlt_response_pkt(host, pkt); + break; + } + + case NOTIFY_ACK_TYPE: + { + struct scsi_qla_host *host = vha; + struct nack_to_isp *entry = (struct nack_to_isp *)pkt; + + if (0xFF != entry->u.isp24.vp_index) { + host = qlt_find_host_by_vp_idx(vha, + entry->u.isp24.vp_index); + if (unlikely(!host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe043, + "qla_target(%d): Response " + "pkt (NOTIFY_ACK_TYPE) " + "received, with unknown " + "vp_index %d\n", vha->vp_idx, + entry->u.isp24.vp_index); + break; + } + } + qlt_response_pkt(host, pkt); + break; + } + + case ABTS_RECV_24XX: + { + struct abts_recv_from_24xx *entry = + (struct abts_recv_from_24xx *)pkt; + struct scsi_qla_host *host = qlt_find_host_by_vp_idx(vha, + entry->vp_index); + if (unlikely(!host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe044, + "qla_target(%d): Response pkt " + "(ABTS_RECV_24XX) received, with unknown " + "vp_index %d\n", vha->vp_idx, entry->vp_index); + break; + } + qlt_response_pkt(host, pkt); + break; + } + + case ABTS_RESP_24XX: + { + struct abts_resp_to_24xx *entry = + (struct abts_resp_to_24xx *)pkt; + struct scsi_qla_host *host = qlt_find_host_by_vp_idx(vha, + entry->vp_index); + if (unlikely(!host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe045, + "qla_target(%d): Response pkt " + "(ABTS_RECV_24XX) received, with unknown " + "vp_index %d\n", vha->vp_idx, entry->vp_index); + break; + } + qlt_response_pkt(host, pkt); + break; + } + + default: + qlt_response_pkt(vha, pkt); + break; + } + +} + +static void qlt_free_session_done(struct work_struct *work) +{ + struct qla_tgt_sess *sess = container_of(work, struct qla_tgt_sess, + free_work); + struct qla_tgt *tgt = sess->tgt; + struct scsi_qla_host *vha = sess->vha; + struct qla_hw_data *ha = vha->hw; + + BUG_ON(!tgt); + /* + * Release the target session for FC Nexus from fabric module code. + */ + if (sess->se_sess != NULL) + ha->tgt.tgt_ops->free_session(sess); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf001, + "Unregistration of sess %p finished\n", sess); + + kfree(sess); + /* + * We need to protect against race, when tgt is freed before or + * inside wake_up() + */ + tgt->sess_count--; + if (tgt->sess_count == 0) + wake_up_all(&tgt->waitQ); +} + +/* ha->hardware_lock supposed to be held on entry */ +void qlt_unreg_sess(struct qla_tgt_sess *sess) +{ + struct scsi_qla_host *vha = sess->vha; + + vha->hw->tgt.tgt_ops->clear_nacl_from_fcport_map(sess); + + list_del(&sess->sess_list_entry); + if (sess->deleted) + list_del(&sess->del_list_entry); + + INIT_WORK(&sess->free_work, qlt_free_session_done); + schedule_work(&sess->free_work); +} +EXPORT_SYMBOL(qlt_unreg_sess); + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess = NULL; + uint32_t unpacked_lun, lun = 0; + uint16_t loop_id; + int res = 0; + struct imm_ntfy_from_isp *n = (struct imm_ntfy_from_isp *)iocb; + struct atio_from_isp *a = (struct atio_from_isp *)iocb; + + loop_id = le16_to_cpu(n->u.isp24.nport_handle); + if (loop_id == 0xFFFF) { +#if 0 /* FIXME: Re-enable Global event handling.. */ + /* Global event */ + atomic_inc(&ha->tgt.qla_tgt->tgt_global_resets_count); + qlt_clear_tgt_db(ha->tgt.qla_tgt, 1); + if (!list_empty(&ha->tgt.qla_tgt->sess_list)) { + sess = list_entry(ha->tgt.qla_tgt->sess_list.next, + typeof(*sess), sess_list_entry); + switch (mcmd) { + case QLA_TGT_NEXUS_LOSS_SESS: + mcmd = QLA_TGT_NEXUS_LOSS; + break; + case QLA_TGT_ABORT_ALL_SESS: + mcmd = QLA_TGT_ABORT_ALL; + break; + case QLA_TGT_NEXUS_LOSS: + case QLA_TGT_ABORT_ALL: + break; + default: + ql_dbg(ql_dbg_tgt, vha, 0xe046, + "qla_target(%d): Not allowed " + "command %x in %s", vha->vp_idx, + mcmd, __func__); + sess = NULL; + break; + } + } else + sess = NULL; +#endif + } else { + sess = ha->tgt.tgt_ops->find_sess_by_loop_id(vha, loop_id); + } + + ql_dbg(ql_dbg_tgt, vha, 0xe000, + "Using sess for qla_tgt_reset: %p\n", sess); + if (!sess) { + res = -ESRCH; + return res; + } + + ql_dbg(ql_dbg_tgt, vha, 0xe047, + "scsi(%ld): resetting (session %p from port " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, " + "mcmd %x, loop_id %d)\n", vha->host_no, sess, + sess->port_name[0], sess->port_name[1], + sess->port_name[2], sess->port_name[3], + sess->port_name[4], sess->port_name[5], + sess->port_name[6], sess->port_name[7], + mcmd, loop_id); + + lun = a->u.isp24.fcp_cmnd.lun; + unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun); + + return qlt_issue_task_mgmt(sess, unpacked_lun, mcmd, + iocb, QLA24XX_MGMT_SEND_NACK); +} + +/* ha->hardware_lock supposed to be held on entry */ +static void qlt_schedule_sess_for_deletion(struct qla_tgt_sess *sess, + bool immediate) +{ + struct qla_tgt *tgt = sess->tgt; + uint32_t dev_loss_tmo = tgt->ha->port_down_retry_count + 5; + + if (sess->deleted) + return; + + ql_dbg(ql_dbg_tgt, sess->vha, 0xe001, + "Scheduling sess %p for deletion\n", sess); + list_add_tail(&sess->del_list_entry, &tgt->del_sess_list); + sess->deleted = 1; + + if (immediate) + dev_loss_tmo = 0; + + sess->expires = jiffies + dev_loss_tmo * HZ; + + ql_dbg(ql_dbg_tgt, sess->vha, 0xe048, + "qla_target(%d): session for port %02x:%02x:%02x:" + "%02x:%02x:%02x:%02x:%02x (loop ID %d) scheduled for " + "deletion in %u secs (expires: %lu) immed: %d\n", + sess->vha->vp_idx, + sess->port_name[0], sess->port_name[1], + sess->port_name[2], sess->port_name[3], + sess->port_name[4], sess->port_name[5], + sess->port_name[6], sess->port_name[7], + sess->loop_id, dev_loss_tmo, sess->expires, immediate); + + if (immediate) + schedule_delayed_work(&tgt->sess_del_work, 0); + else + schedule_delayed_work(&tgt->sess_del_work, + jiffies - sess->expires); +} + +/* ha->hardware_lock supposed to be held on entry */ +static void qlt_clear_tgt_db(struct qla_tgt *tgt, bool local_only) +{ + struct qla_tgt_sess *sess; + + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) + qlt_schedule_sess_for_deletion(sess, true); + + /* At this point tgt could be already dead */ +} + +static int qla24xx_get_loop_id(struct scsi_qla_host *vha, const uint8_t *s_id, + uint16_t *loop_id) +{ + struct qla_hw_data *ha = vha->hw; + dma_addr_t gid_list_dma; + struct gid_list_info *gid_list; + char *id_iter; + int res, rc, i; + uint16_t entries; + + gid_list = dma_alloc_coherent(&ha->pdev->dev, qla2x00_gid_list_size(ha), + &gid_list_dma, GFP_KERNEL); + if (!gid_list) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf044, + "qla_target(%d): DMA Alloc failed of %u\n", + vha->vp_idx, qla2x00_gid_list_size(ha)); + return -ENOMEM; + } + + /* Get list of logged in devices */ + rc = qla2x00_get_id_list(vha, gid_list, gid_list_dma, &entries); + if (rc != QLA_SUCCESS) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf045, + "qla_target(%d): get_id_list() failed: %x\n", + vha->vp_idx, rc); + res = -1; + goto out_free_id_list; + } + + id_iter = (char *)gid_list; + res = -1; + for (i = 0; i < entries; i++) { + struct gid_list_info *gid = (struct gid_list_info *)id_iter; + if ((gid->al_pa == s_id[2]) && + (gid->area == s_id[1]) && + (gid->domain == s_id[0])) { + *loop_id = le16_to_cpu(gid->loop_id); + res = 0; + break; + } + id_iter += ha->gid_list_info_size; + } + +out_free_id_list: + dma_free_coherent(&ha->pdev->dev, qla2x00_gid_list_size(ha), + gid_list, gid_list_dma); + return res; +} + +static bool qlt_check_fcport_exist(struct scsi_qla_host *vha, + struct qla_tgt_sess *sess) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_port_24xx_data *pmap24; + bool res, found = false; + int rc, i; + uint16_t loop_id = 0xFFFF; /* to eliminate compiler's warning */ + uint16_t entries; + void *pmap; + int pmap_len; + fc_port_t *fcport; + int global_resets; + +retry: + global_resets = atomic_read(&ha->tgt.qla_tgt->tgt_global_resets_count); + + rc = qla2x00_get_node_name_list(vha, &pmap, &pmap_len); + if (rc != QLA_SUCCESS) { + res = false; + goto out; + } + + pmap24 = pmap; + entries = pmap_len/sizeof(*pmap24); + + for (i = 0; i < entries; ++i) { + if (!memcmp(sess->port_name, pmap24[i].port_name, WWN_SIZE)) { + loop_id = le16_to_cpu(pmap24[i].loop_id); + found = true; + break; + } + } + + kfree(pmap); + + if (!found) { + res = false; + goto out; + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf046, + "qlt_check_fcport_exist(): loop_id %d", loop_id); + + fcport = kzalloc(sizeof(*fcport), GFP_KERNEL); + if (fcport == NULL) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf047, + "qla_target(%d): Allocation of tmp FC port failed", + vha->vp_idx); + res = false; + goto out; + } + + fcport->loop_id = loop_id; + + rc = qla2x00_get_port_database(vha, fcport, 0); + if (rc != QLA_SUCCESS) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf048, + "qla_target(%d): Failed to retrieve fcport " + "information -- get_port_database() returned %x " + "(loop_id=0x%04x)", vha->vp_idx, rc, loop_id); + res = false; + goto out_free_fcport; + } + + if (global_resets != + atomic_read(&ha->tgt.qla_tgt->tgt_global_resets_count)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf002, + "qla_target(%d): global reset during session discovery" + " (counter was %d, new %d), retrying", + vha->vp_idx, global_resets, + atomic_read(&ha->tgt.qla_tgt->tgt_global_resets_count)); + goto retry; + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf003, + "Updating sess %p s_id %x:%x:%x, loop_id %d) to d_id %x:%x:%x, " + "loop_id %d", sess, sess->s_id.b.domain, sess->s_id.b.al_pa, + sess->s_id.b.area, sess->loop_id, fcport->d_id.b.domain, + fcport->d_id.b.al_pa, fcport->d_id.b.area, fcport->loop_id); + + sess->s_id = fcport->d_id; + sess->loop_id = fcport->loop_id; + sess->conf_compl_supported = !!(fcport->flags & + FCF_CONF_COMP_SUPPORTED); + + res = true; + +out_free_fcport: + kfree(fcport); + +out: + return res; +} + +/* ha->hardware_lock supposed to be held on entry */ +static void qlt_undelete_sess(struct qla_tgt_sess *sess) +{ + BUG_ON(!sess->deleted); + + list_del(&sess->del_list_entry); + sess->deleted = 0; +} + +static void qlt_del_sess_work_fn(struct delayed_work *work) +{ + struct qla_tgt *tgt = container_of(work, struct qla_tgt, + sess_del_work); + struct scsi_qla_host *vha = tgt->vha; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess; + unsigned long flags; + + spin_lock_irqsave(&ha->hardware_lock, flags); + while (!list_empty(&tgt->del_sess_list)) { + sess = list_entry(tgt->del_sess_list.next, typeof(*sess), + del_list_entry); + if (time_after_eq(jiffies, sess->expires)) { + bool cancel; + + qlt_undelete_sess(sess); + + spin_unlock_irqrestore(&ha->hardware_lock, flags); + cancel = qlt_check_fcport_exist(vha, sess); + + if (cancel) { + if (sess->deleted) { + /* + * sess was again deleted while we were + * discovering it + */ + spin_lock_irqsave(&ha->hardware_lock, + flags); + continue; + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf049, + "qla_target(%d): cancel deletion of " + "session for port %02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x (loop ID %d), because " + " it isn't deleted by firmware", + vha->vp_idx, sess->port_name[0], + sess->port_name[1], sess->port_name[2], + sess->port_name[3], sess->port_name[4], + sess->port_name[5], sess->port_name[6], + sess->port_name[7], sess->loop_id); + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf004, + "Timeout: sess %p about to be deleted\n", + sess); + ha->tgt.tgt_ops->shutdown_sess(sess); + ha->tgt.tgt_ops->put_sess(sess); + } + + spin_lock_irqsave(&ha->hardware_lock, flags); + } else { + schedule_delayed_work(&tgt->sess_del_work, + jiffies - sess->expires); + break; + } + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +/* + * Adds an extra ref to allow to drop hw lock after adding sess to the list. + * Caller must put it. + */ +static struct qla_tgt_sess *qlt_create_sess( + struct scsi_qla_host *vha, + fc_port_t *fcport, + bool local) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess; + unsigned long flags; + unsigned char be_sid[3]; + + /* Check to avoid double sessions */ + spin_lock_irqsave(&ha->hardware_lock, flags); + list_for_each_entry(sess, &ha->tgt.qla_tgt->sess_list, + sess_list_entry) { + if (!memcmp(sess->port_name, fcport->port_name, WWN_SIZE)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf005, + "Double sess %p found (s_id %x:%x:%x, " + "loop_id %d), updating to d_id %x:%x:%x, " + "loop_id %d", sess, sess->s_id.b.domain, + sess->s_id.b.al_pa, sess->s_id.b.area, + sess->loop_id, fcport->d_id.b.domain, + fcport->d_id.b.al_pa, fcport->d_id.b.area, + fcport->loop_id); + + if (sess->deleted) + qlt_undelete_sess(sess); + + kref_get(&sess->se_sess->sess_kref); + sess->s_id = fcport->d_id; + sess->loop_id = fcport->loop_id; + sess->conf_compl_supported = !!(fcport->flags & + FCF_CONF_COMP_SUPPORTED); + if (sess->local && !local) + sess->local = 0; + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return sess; + } + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + sess = kzalloc(sizeof(*sess), GFP_KERNEL); + if (!sess) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04a, + "qla_target(%u): session allocation failed, " + "all commands from port %02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x will be refused", vha->vp_idx, + fcport->port_name[0], fcport->port_name[1], + fcport->port_name[2], fcport->port_name[3], + fcport->port_name[4], fcport->port_name[5], + fcport->port_name[6], fcport->port_name[7]); + + return NULL; + } + sess->tgt = ha->tgt.qla_tgt; + sess->vha = vha; + sess->s_id = fcport->d_id; + sess->loop_id = fcport->loop_id; + sess->local = local; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf006, + "Adding sess %p to tgt %p via ->check_initiator_node_acl()\n", + sess, ha->tgt.qla_tgt); + + be_sid[0] = sess->s_id.b.domain; + be_sid[1] = sess->s_id.b.area; + be_sid[2] = sess->s_id.b.al_pa; + /* + * Determine if this fc_port->port_name is allowed to access + * target mode using explict NodeACLs+MappedLUNs, or using + * TPG demo mode. If this is successful a target mode FC nexus + * is created. + */ + if (ha->tgt.tgt_ops->check_initiator_node_acl(vha, + &fcport->port_name[0], sess, &be_sid[0], fcport->loop_id) < 0) { + kfree(sess); + return NULL; + } + /* + * Take an extra reference to ->sess_kref here to handle qla_tgt_sess + * access across ->hardware_lock reaquire. + */ + kref_get(&sess->se_sess->sess_kref); + + sess->conf_compl_supported = !!(fcport->flags & + FCF_CONF_COMP_SUPPORTED); + BUILD_BUG_ON(sizeof(sess->port_name) != sizeof(fcport->port_name)); + memcpy(sess->port_name, fcport->port_name, sizeof(sess->port_name)); + + spin_lock_irqsave(&ha->hardware_lock, flags); + list_add_tail(&sess->sess_list_entry, &ha->tgt.qla_tgt->sess_list); + ha->tgt.qla_tgt->sess_count++; + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04b, + "qla_target(%d): %ssession for wwn %02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x (loop_id %d, s_id %x:%x:%x, confirmed" + " completion %ssupported) added\n", + vha->vp_idx, local ? "local " : "", fcport->port_name[0], + fcport->port_name[1], fcport->port_name[2], fcport->port_name[3], + fcport->port_name[4], fcport->port_name[5], fcport->port_name[6], + fcport->port_name[7], fcport->loop_id, sess->s_id.b.domain, + sess->s_id.b.area, sess->s_id.b.al_pa, sess->conf_compl_supported ? + "" : "not "); + + return sess; +} + +/* + * Called from drivers/scsi/qla2xxx/qla_init.c:qla2x00_reg_remote_port() + */ +void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + struct qla_tgt_sess *sess; + unsigned long flags; + + if (!vha->hw->tgt.tgt_ops) + return; + + if (!tgt || (fcport->port_type != FCT_INITIATOR)) + return; + + spin_lock_irqsave(&ha->hardware_lock, flags); + if (tgt->tgt_stop) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + return; + } + sess = qlt_find_sess_by_port_name(tgt, fcport->port_name); + if (!sess) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + mutex_lock(&ha->tgt.tgt_mutex); + sess = qlt_create_sess(vha, fcport, false); + mutex_unlock(&ha->tgt.tgt_mutex); + + spin_lock_irqsave(&ha->hardware_lock, flags); + } else { + kref_get(&sess->se_sess->sess_kref); + + if (sess->deleted) { + qlt_undelete_sess(sess); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04c, + "qla_target(%u): %ssession for port %02x:" + "%02x:%02x:%02x:%02x:%02x:%02x:%02x (loop ID %d) " + "reappeared\n", vha->vp_idx, sess->local ? "local " + : "", sess->port_name[0], sess->port_name[1], + sess->port_name[2], sess->port_name[3], + sess->port_name[4], sess->port_name[5], + sess->port_name[6], sess->port_name[7], + sess->loop_id); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf007, + "Reappeared sess %p\n", sess); + } + sess->s_id = fcport->d_id; + sess->loop_id = fcport->loop_id; + sess->conf_compl_supported = !!(fcport->flags & + FCF_CONF_COMP_SUPPORTED); + } + + if (sess && sess->local) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04d, + "qla_target(%u): local session for " + "port %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x " + "(loop ID %d) became global\n", vha->vp_idx, + fcport->port_name[0], fcport->port_name[1], + fcport->port_name[2], fcport->port_name[3], + fcport->port_name[4], fcport->port_name[5], + fcport->port_name[6], fcport->port_name[7], + sess->loop_id); + sess->local = 0; + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + ha->tgt.tgt_ops->put_sess(sess); +} + +void qlt_fc_port_deleted(struct scsi_qla_host *vha, fc_port_t *fcport) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + struct qla_tgt_sess *sess; + unsigned long flags; + + if (!vha->hw->tgt.tgt_ops) + return; + + if (!tgt || (fcport->port_type != FCT_INITIATOR)) + return; + + spin_lock_irqsave(&ha->hardware_lock, flags); + if (tgt->tgt_stop) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + return; + } + sess = qlt_find_sess_by_port_name(tgt, fcport->port_name); + if (!sess) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + return; + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf008, "qla_tgt_fc_port_deleted %p", sess); + + sess->local = 1; + qlt_schedule_sess_for_deletion(sess, false); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +static inline int test_tgt_sess_count(struct qla_tgt *tgt) +{ + struct qla_hw_data *ha = tgt->ha; + unsigned long flags; + int res; + /* + * We need to protect against race, when tgt is freed before or + * inside wake_up() + */ + spin_lock_irqsave(&ha->hardware_lock, flags); + ql_dbg(ql_dbg_tgt, tgt->vha, 0xe002, + "tgt %p, empty(sess_list)=%d sess_count=%d\n", + tgt, list_empty(&tgt->sess_list), tgt->sess_count); + res = (tgt->sess_count == 0); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return res; +} + +/* Called by tcm_qla2xxx configfs code */ +void qlt_stop_phase1(struct qla_tgt *tgt) +{ + struct scsi_qla_host *vha = tgt->vha; + struct qla_hw_data *ha = tgt->ha; + unsigned long flags; + + if (tgt->tgt_stop || tgt->tgt_stopped) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04e, + "Already in tgt->tgt_stop or tgt_stopped state\n"); + dump_stack(); + return; + } + + ql_dbg(ql_dbg_tgt, vha, 0xe003, "Stopping target for host %ld(%p)\n", + vha->host_no, vha); + /* + * Mutex needed to sync with qla_tgt_fc_port_[added,deleted]. + * Lock is needed, because we still can get an incoming packet. + */ + mutex_lock(&ha->tgt.tgt_mutex); + spin_lock_irqsave(&ha->hardware_lock, flags); + tgt->tgt_stop = 1; + qlt_clear_tgt_db(tgt, true); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + mutex_unlock(&ha->tgt.tgt_mutex); + + flush_delayed_work_sync(&tgt->sess_del_work); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf009, + "Waiting for sess works (tgt %p)", tgt); + spin_lock_irqsave(&tgt->sess_work_lock, flags); + while (!list_empty(&tgt->sess_works_list)) { + spin_unlock_irqrestore(&tgt->sess_work_lock, flags); + flush_scheduled_work(); + spin_lock_irqsave(&tgt->sess_work_lock, flags); + } + spin_unlock_irqrestore(&tgt->sess_work_lock, flags); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00a, + "Waiting for tgt %p: list_empty(sess_list)=%d " + "sess_count=%d\n", tgt, list_empty(&tgt->sess_list), + tgt->sess_count); + + wait_event(tgt->waitQ, test_tgt_sess_count(tgt)); + + /* Big hammer */ + if (!ha->flags.host_shutting_down && qla_tgt_mode_enabled(vha)) + qlt_disable_vha(vha); + + /* Wait for sessions to clear out (just in case) */ + wait_event(tgt->waitQ, test_tgt_sess_count(tgt)); +} +EXPORT_SYMBOL(qlt_stop_phase1); + +/* Called by tcm_qla2xxx configfs code */ +void qlt_stop_phase2(struct qla_tgt *tgt) +{ + struct qla_hw_data *ha = tgt->ha; + unsigned long flags; + + if (tgt->tgt_stopped) { + ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf04f, + "Already in tgt->tgt_stopped state\n"); + dump_stack(); + return; + } + + ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf00b, + "Waiting for %d IRQ commands to complete (tgt %p)", + tgt->irq_cmd_count, tgt); + + mutex_lock(&ha->tgt.tgt_mutex); + spin_lock_irqsave(&ha->hardware_lock, flags); + while (tgt->irq_cmd_count != 0) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + udelay(2); + spin_lock_irqsave(&ha->hardware_lock, flags); + } + tgt->tgt_stop = 0; + tgt->tgt_stopped = 1; + spin_unlock_irqrestore(&ha->hardware_lock, flags); + mutex_unlock(&ha->tgt.tgt_mutex); + + ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf00c, "Stop of tgt %p finished", + tgt); +} +EXPORT_SYMBOL(qlt_stop_phase2); + +/* Called from qlt_remove_target() -> qla2x00_remove_one() */ +void qlt_release(struct qla_tgt *tgt) +{ + struct qla_hw_data *ha = tgt->ha; + + if ((ha->tgt.qla_tgt != NULL) && !tgt->tgt_stopped) + qlt_stop_phase2(tgt); + + ha->tgt.qla_tgt = NULL; + + ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf00d, + "Release of tgt %p finished\n", tgt); + + kfree(tgt); +} + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_sched_sess_work(struct qla_tgt *tgt, int type, + const void *param, unsigned int param_size) +{ + struct qla_tgt_sess_work_param *prm; + unsigned long flags; + + prm = kzalloc(sizeof(*prm), GFP_ATOMIC); + if (!prm) { + ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf050, + "qla_target(%d): Unable to create session " + "work, command will be refused", 0); + return -ENOMEM; + } + + ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf00e, + "Scheduling work (type %d, prm %p)" + " to find session for param %p (size %d, tgt %p)\n", + type, prm, param, param_size, tgt); + + prm->type = type; + memcpy(&prm->tm_iocb, param, param_size); + + spin_lock_irqsave(&tgt->sess_work_lock, flags); + list_add_tail(&prm->sess_works_list_entry, &tgt->sess_works_list); + spin_unlock_irqrestore(&tgt->sess_work_lock, flags); + + schedule_work(&tgt->sess_work); + + return 0; +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_send_notify_ack(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *ntfy, + uint32_t add_flags, uint16_t resp_code, int resp_code_valid, + uint16_t srr_flags, uint16_t srr_reject_code, uint8_t srr_explan) +{ + struct qla_hw_data *ha = vha->hw; + request_t *pkt; + struct nack_to_isp *nack; + + ql_dbg(ql_dbg_tgt, vha, 0xe004, "Sending NOTIFY_ACK (ha=%p)\n", ha); + + /* Send marker if required */ + if (qlt_issue_marker(vha, 1) != QLA_SUCCESS) + return; + + pkt = (request_t *)qla2x00_alloc_iocbs(vha, NULL); + if (!pkt) { + ql_dbg(ql_dbg_tgt, vha, 0xe049, + "qla_target(%d): %s failed: unable to allocate " + "request packet\n", vha->vp_idx, __func__); + return; + } + + if (ha->tgt.qla_tgt != NULL) + ha->tgt.qla_tgt->notify_ack_expected++; + + pkt->entry_type = NOTIFY_ACK_TYPE; + pkt->entry_count = 1; + + nack = (struct nack_to_isp *)pkt; + nack->ox_id = ntfy->ox_id; + + nack->u.isp24.nport_handle = ntfy->u.isp24.nport_handle; + if (le16_to_cpu(ntfy->u.isp24.status) == IMM_NTFY_ELS) { + nack->u.isp24.flags = ntfy->u.isp24.flags & + __constant_cpu_to_le32(NOTIFY24XX_FLAGS_PUREX_IOCB); + } + nack->u.isp24.srr_rx_id = ntfy->u.isp24.srr_rx_id; + nack->u.isp24.status = ntfy->u.isp24.status; + nack->u.isp24.status_subcode = ntfy->u.isp24.status_subcode; + nack->u.isp24.exchange_address = ntfy->u.isp24.exchange_address; + nack->u.isp24.srr_rel_offs = ntfy->u.isp24.srr_rel_offs; + nack->u.isp24.srr_ui = ntfy->u.isp24.srr_ui; + nack->u.isp24.srr_flags = cpu_to_le16(srr_flags); + nack->u.isp24.srr_reject_code = srr_reject_code; + nack->u.isp24.srr_reject_code_expl = srr_explan; + nack->u.isp24.vp_index = ntfy->u.isp24.vp_index; + + ql_dbg(ql_dbg_tgt, vha, 0xe005, + "qla_target(%d): Sending 24xx Notify Ack %d\n", + vha->vp_idx, nack->u.isp24.status); + + qla2x00_start_iocbs(vha, vha->req); +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_24xx_send_abts_resp(struct scsi_qla_host *vha, + struct abts_recv_from_24xx *abts, uint32_t status, + bool ids_reversed) +{ + struct qla_hw_data *ha = vha->hw; + struct abts_resp_to_24xx *resp; + uint32_t f_ctl; + uint8_t *p; + + ql_dbg(ql_dbg_tgt, vha, 0xe006, + "Sending task mgmt ABTS response (ha=%p, atio=%p, status=%x\n", + ha, abts, status); + + /* Send marker if required */ + if (qlt_issue_marker(vha, 1) != QLA_SUCCESS) + return; + + resp = (struct abts_resp_to_24xx *)qla2x00_alloc_iocbs(vha, NULL); + if (!resp) { + ql_dbg(ql_dbg_tgt, vha, 0xe04a, + "qla_target(%d): %s failed: unable to allocate " + "request packet", vha->vp_idx, __func__); + return; + } + + resp->entry_type = ABTS_RESP_24XX; + resp->entry_count = 1; + resp->nport_handle = abts->nport_handle; + resp->vp_index = vha->vp_idx; + resp->sof_type = abts->sof_type; + resp->exchange_address = abts->exchange_address; + resp->fcp_hdr_le = abts->fcp_hdr_le; + f_ctl = __constant_cpu_to_le32(F_CTL_EXCH_CONTEXT_RESP | + F_CTL_LAST_SEQ | F_CTL_END_SEQ | + F_CTL_SEQ_INITIATIVE); + p = (uint8_t *)&f_ctl; + resp->fcp_hdr_le.f_ctl[0] = *p++; + resp->fcp_hdr_le.f_ctl[1] = *p++; + resp->fcp_hdr_le.f_ctl[2] = *p; + if (ids_reversed) { + resp->fcp_hdr_le.d_id[0] = abts->fcp_hdr_le.d_id[0]; + resp->fcp_hdr_le.d_id[1] = abts->fcp_hdr_le.d_id[1]; + resp->fcp_hdr_le.d_id[2] = abts->fcp_hdr_le.d_id[2]; + resp->fcp_hdr_le.s_id[0] = abts->fcp_hdr_le.s_id[0]; + resp->fcp_hdr_le.s_id[1] = abts->fcp_hdr_le.s_id[1]; + resp->fcp_hdr_le.s_id[2] = abts->fcp_hdr_le.s_id[2]; + } else { + resp->fcp_hdr_le.d_id[0] = abts->fcp_hdr_le.s_id[0]; + resp->fcp_hdr_le.d_id[1] = abts->fcp_hdr_le.s_id[1]; + resp->fcp_hdr_le.d_id[2] = abts->fcp_hdr_le.s_id[2]; + resp->fcp_hdr_le.s_id[0] = abts->fcp_hdr_le.d_id[0]; + resp->fcp_hdr_le.s_id[1] = abts->fcp_hdr_le.d_id[1]; + resp->fcp_hdr_le.s_id[2] = abts->fcp_hdr_le.d_id[2]; + } + resp->exchange_addr_to_abort = abts->exchange_addr_to_abort; + if (status == FCP_TMF_CMPL) { + resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_ACC; + resp->payload.ba_acct.seq_id_valid = SEQ_ID_INVALID; + resp->payload.ba_acct.low_seq_cnt = 0x0000; + resp->payload.ba_acct.high_seq_cnt = 0xFFFF; + resp->payload.ba_acct.ox_id = abts->fcp_hdr_le.ox_id; + resp->payload.ba_acct.rx_id = abts->fcp_hdr_le.rx_id; + } else { + resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_RJT; + resp->payload.ba_rjt.reason_code = + BA_RJT_REASON_CODE_UNABLE_TO_PERFORM; + /* Other bytes are zero */ + } + + ha->tgt.qla_tgt->abts_resp_expected++; + + qla2x00_start_iocbs(vha, vha->req); +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_24xx_retry_term_exchange(struct scsi_qla_host *vha, + struct abts_resp_from_24xx_fw *entry) +{ + struct ctio7_to_24xx *ctio; + + ql_dbg(ql_dbg_tgt, vha, 0xe007, + "Sending retry TERM EXCH CTIO7 (ha=%p)\n", vha->hw); + /* Send marker if required */ + if (qlt_issue_marker(vha, 1) != QLA_SUCCESS) + return; + + ctio = (struct ctio7_to_24xx *)qla2x00_alloc_iocbs(vha, NULL); + if (ctio == NULL) { + ql_dbg(ql_dbg_tgt, vha, 0xe04b, + "qla_target(%d): %s failed: unable to allocate " + "request packet\n", vha->vp_idx, __func__); + return; + } + + /* + * We've got on entrance firmware's response on by us generated + * ABTS response. So, in it ID fields are reversed. + */ + + ctio->entry_type = CTIO_TYPE7; + ctio->entry_count = 1; + ctio->nport_handle = entry->nport_handle; + ctio->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK; + ctio->timeout = __constant_cpu_to_le16(QLA_TGT_TIMEOUT); + ctio->vp_index = vha->vp_idx; + ctio->initiator_id[0] = entry->fcp_hdr_le.d_id[0]; + ctio->initiator_id[1] = entry->fcp_hdr_le.d_id[1]; + ctio->initiator_id[2] = entry->fcp_hdr_le.d_id[2]; + ctio->exchange_addr = entry->exchange_addr_to_abort; + ctio->u.status1.flags = + __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1 | + CTIO7_FLAGS_TERMINATE); + ctio->u.status1.ox_id = entry->fcp_hdr_le.ox_id; + + qla2x00_start_iocbs(vha, vha->req); + + qlt_24xx_send_abts_resp(vha, (struct abts_recv_from_24xx *)entry, + FCP_TMF_CMPL, true); +} + +/* ha->hardware_lock supposed to be held on entry */ +static int __qlt_24xx_handle_abts(struct scsi_qla_host *vha, + struct abts_recv_from_24xx *abts, struct qla_tgt_sess *sess) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_mgmt_cmd *mcmd; + int rc; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00f, + "qla_target(%d): task abort (tag=%d)\n", + vha->vp_idx, abts->exchange_addr_to_abort); + + mcmd = mempool_alloc(qla_tgt_mgmt_cmd_mempool, GFP_ATOMIC); + if (mcmd == NULL) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf051, + "qla_target(%d): %s: Allocation of ABORT cmd failed", + vha->vp_idx, __func__); + return -ENOMEM; + } + memset(mcmd, 0, sizeof(*mcmd)); + + mcmd->sess = sess; + memcpy(&mcmd->orig_iocb.abts, abts, sizeof(mcmd->orig_iocb.abts)); + + rc = ha->tgt.tgt_ops->handle_tmr(mcmd, 0, TMR_ABORT_TASK, + abts->exchange_addr_to_abort); + if (rc != 0) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf052, + "qla_target(%d): tgt_ops->handle_tmr()" + " failed: %d", vha->vp_idx, rc); + mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool); + return -EFAULT; + } + + return 0; +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_24xx_handle_abts(struct scsi_qla_host *vha, + struct abts_recv_from_24xx *abts) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess; + uint32_t tag = abts->exchange_addr_to_abort; + uint8_t s_id[3]; + int rc; + + if (le32_to_cpu(abts->fcp_hdr_le.parameter) & ABTS_PARAM_ABORT_SEQ) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf053, + "qla_target(%d): ABTS: Abort Sequence not " + "supported\n", vha->vp_idx); + qlt_24xx_send_abts_resp(vha, abts, FCP_TMF_REJECTED, false); + return; + } + + if (tag == ATIO_EXCHANGE_ADDRESS_UNKNOWN) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf010, + "qla_target(%d): ABTS: Unknown Exchange " + "Address received\n", vha->vp_idx); + qlt_24xx_send_abts_resp(vha, abts, FCP_TMF_REJECTED, false); + return; + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf011, + "qla_target(%d): task abort (s_id=%x:%x:%x, " + "tag=%d, param=%x)\n", vha->vp_idx, abts->fcp_hdr_le.s_id[2], + abts->fcp_hdr_le.s_id[1], abts->fcp_hdr_le.s_id[0], tag, + le32_to_cpu(abts->fcp_hdr_le.parameter)); + + s_id[0] = abts->fcp_hdr_le.s_id[2]; + s_id[1] = abts->fcp_hdr_le.s_id[1]; + s_id[2] = abts->fcp_hdr_le.s_id[0]; + + sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, s_id); + if (!sess) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf012, + "qla_target(%d): task abort for non-existant session\n", + vha->vp_idx); + rc = qlt_sched_sess_work(ha->tgt.qla_tgt, + QLA_TGT_SESS_WORK_ABORT, abts, sizeof(*abts)); + if (rc != 0) { + qlt_24xx_send_abts_resp(vha, abts, FCP_TMF_REJECTED, + false); + } + return; + } + + rc = __qlt_24xx_handle_abts(vha, abts, sess); + if (rc != 0) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf054, + "qla_target(%d): __qlt_24xx_handle_abts() failed: %d\n", + vha->vp_idx, rc); + qlt_24xx_send_abts_resp(vha, abts, FCP_TMF_REJECTED, false); + return; + } +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_24xx_send_task_mgmt_ctio(struct scsi_qla_host *ha, + struct qla_tgt_mgmt_cmd *mcmd, uint32_t resp_code) +{ + struct atio_from_isp *atio = &mcmd->orig_iocb.atio; + struct ctio7_to_24xx *ctio; + + ql_dbg(ql_dbg_tgt, ha, 0xe008, + "Sending task mgmt CTIO7 (ha=%p, atio=%p, resp_code=%x\n", + ha, atio, resp_code); + + /* Send marker if required */ + if (qlt_issue_marker(ha, 1) != QLA_SUCCESS) + return; + + ctio = (struct ctio7_to_24xx *)qla2x00_alloc_iocbs(ha, NULL); + if (ctio == NULL) { + ql_dbg(ql_dbg_tgt, ha, 0xe04c, + "qla_target(%d): %s failed: unable to allocate " + "request packet\n", ha->vp_idx, __func__); + return; + } + + ctio->entry_type = CTIO_TYPE7; + ctio->entry_count = 1; + ctio->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK; + ctio->nport_handle = mcmd->sess->loop_id; + ctio->timeout = __constant_cpu_to_le16(QLA_TGT_TIMEOUT); + ctio->vp_index = ha->vp_idx; + ctio->initiator_id[0] = atio->u.isp24.fcp_hdr.s_id[2]; + ctio->initiator_id[1] = atio->u.isp24.fcp_hdr.s_id[1]; + ctio->initiator_id[2] = atio->u.isp24.fcp_hdr.s_id[0]; + ctio->exchange_addr = atio->u.isp24.exchange_addr; + ctio->u.status1.flags = (atio->u.isp24.attr << 9) | + __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1 | + CTIO7_FLAGS_SEND_STATUS); + ctio->u.status1.ox_id = swab16(atio->u.isp24.fcp_hdr.ox_id); + ctio->u.status1.scsi_status = + __constant_cpu_to_le16(SS_RESPONSE_INFO_LEN_VALID); + ctio->u.status1.response_len = __constant_cpu_to_le16(8); + ((uint32_t *)ctio->u.status1.sense_data)[0] = cpu_to_be32(resp_code); + + qla2x00_start_iocbs(ha, ha->req); +} + +void qlt_free_mcmd(struct qla_tgt_mgmt_cmd *mcmd) +{ + mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool); +} +EXPORT_SYMBOL(qlt_free_mcmd); + +/* callback from target fabric module code */ +void qlt_xmit_tm_rsp(struct qla_tgt_mgmt_cmd *mcmd) +{ + struct scsi_qla_host *vha = mcmd->sess->vha; + struct qla_hw_data *ha = vha->hw; + unsigned long flags; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf013, + "TM response mcmd (%p) status %#x state %#x", + mcmd, mcmd->fc_tm_rsp, mcmd->flags); + + spin_lock_irqsave(&ha->hardware_lock, flags); + if (mcmd->flags == QLA24XX_MGMT_SEND_NACK) + qlt_send_notify_ack(vha, &mcmd->orig_iocb.imm_ntfy, + 0, 0, 0, 0, 0, 0); + else { + if (mcmd->se_cmd.se_tmr_req->function == TMR_ABORT_TASK) + qlt_24xx_send_abts_resp(vha, &mcmd->orig_iocb.abts, + mcmd->fc_tm_rsp, false); + else + qlt_24xx_send_task_mgmt_ctio(vha, mcmd, + mcmd->fc_tm_rsp); + } + /* + * Make the callback for ->free_mcmd() to queue_work() and invoke + * target_put_sess_cmd() to drop cmd_kref to 1. The final + * target_put_sess_cmd() call will be made from TFO->check_stop_free() + * -> tcm_qla2xxx_check_stop_free() to release the TMR associated se_cmd + * descriptor after TFO->queue_tm_rsp() -> tcm_qla2xxx_queue_tm_rsp() -> + * qlt_xmit_tm_rsp() returns here.. + */ + ha->tgt.tgt_ops->free_mcmd(mcmd); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} +EXPORT_SYMBOL(qlt_xmit_tm_rsp); + +/* No locks */ +static int qlt_pci_map_calc_cnt(struct qla_tgt_prm *prm) +{ + struct qla_tgt_cmd *cmd = prm->cmd; + + BUG_ON(cmd->sg_cnt == 0); + + prm->sg = (struct scatterlist *)cmd->sg; + prm->seg_cnt = pci_map_sg(prm->tgt->ha->pdev, cmd->sg, + cmd->sg_cnt, cmd->dma_data_direction); + if (unlikely(prm->seg_cnt == 0)) + goto out_err; + + prm->cmd->sg_mapped = 1; + + /* + * If greater than four sg entries then we need to allocate + * the continuation entries + */ + if (prm->seg_cnt > prm->tgt->datasegs_per_cmd) + prm->req_cnt += DIV_ROUND_UP(prm->seg_cnt - + prm->tgt->datasegs_per_cmd, prm->tgt->datasegs_per_cont); + + ql_dbg(ql_dbg_tgt, prm->cmd->vha, 0xe009, "seg_cnt=%d, req_cnt=%d\n", + prm->seg_cnt, prm->req_cnt); + return 0; + +out_err: + ql_dbg(ql_dbg_tgt, prm->cmd->vha, 0xe04d, + "qla_target(%d): PCI mapping failed: sg_cnt=%d", + 0, prm->cmd->sg_cnt); + return -1; +} + +static inline void qlt_unmap_sg(struct scsi_qla_host *vha, + struct qla_tgt_cmd *cmd) +{ + struct qla_hw_data *ha = vha->hw; + + BUG_ON(!cmd->sg_mapped); + pci_unmap_sg(ha->pdev, cmd->sg, cmd->sg_cnt, cmd->dma_data_direction); + cmd->sg_mapped = 0; +} + +static int qlt_check_reserve_free_req(struct scsi_qla_host *vha, + uint32_t req_cnt) +{ + struct qla_hw_data *ha = vha->hw; + device_reg_t __iomem *reg = ha->iobase; + uint32_t cnt; + + if (vha->req->cnt < (req_cnt + 2)) { + cnt = (uint16_t)RD_REG_DWORD(®->isp24.req_q_out); + + ql_dbg(ql_dbg_tgt, vha, 0xe00a, + "Request ring circled: cnt=%d, vha->->ring_index=%d, " + "vha->req->cnt=%d, req_cnt=%d\n", cnt, + vha->req->ring_index, vha->req->cnt, req_cnt); + if (vha->req->ring_index < cnt) + vha->req->cnt = cnt - vha->req->ring_index; + else + vha->req->cnt = vha->req->length - + (vha->req->ring_index - cnt); + } + + if (unlikely(vha->req->cnt < (req_cnt + 2))) { + ql_dbg(ql_dbg_tgt, vha, 0xe00b, + "qla_target(%d): There is no room in the " + "request ring: vha->req->ring_index=%d, vha->req->cnt=%d, " + "req_cnt=%d\n", vha->vp_idx, vha->req->ring_index, + vha->req->cnt, req_cnt); + return -EAGAIN; + } + vha->req->cnt -= req_cnt; + + return 0; +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static inline void *qlt_get_req_pkt(struct scsi_qla_host *vha) +{ + /* Adjust ring index. */ + vha->req->ring_index++; + if (vha->req->ring_index == vha->req->length) { + vha->req->ring_index = 0; + vha->req->ring_ptr = vha->req->ring; + } else { + vha->req->ring_ptr++; + } + return (cont_entry_t *)vha->req->ring_ptr; +} + +/* ha->hardware_lock supposed to be held on entry */ +static inline uint32_t qlt_make_handle(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + uint32_t h; + + h = ha->tgt.current_handle; + /* always increment cmd handle */ + do { + ++h; + if (h > MAX_OUTSTANDING_COMMANDS) + h = 1; /* 0 is QLA_TGT_NULL_HANDLE */ + if (h == ha->tgt.current_handle) { + ql_dbg(ql_dbg_tgt, vha, 0xe04e, + "qla_target(%d): Ran out of " + "empty cmd slots in ha %p\n", vha->vp_idx, ha); + h = QLA_TGT_NULL_HANDLE; + break; + } + } while ((h == QLA_TGT_NULL_HANDLE) || + (h == QLA_TGT_SKIP_HANDLE) || + (ha->tgt.cmds[h-1] != NULL)); + + if (h != QLA_TGT_NULL_HANDLE) + ha->tgt.current_handle = h; + + return h; +} + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_24xx_build_ctio_pkt(struct qla_tgt_prm *prm, + struct scsi_qla_host *vha) +{ + uint32_t h; + struct ctio7_to_24xx *pkt; + struct qla_hw_data *ha = vha->hw; + struct atio_from_isp *atio = &prm->cmd->atio; + + pkt = (struct ctio7_to_24xx *)vha->req->ring_ptr; + prm->pkt = pkt; + memset(pkt, 0, sizeof(*pkt)); + + pkt->entry_type = CTIO_TYPE7; + pkt->entry_count = (uint8_t)prm->req_cnt; + pkt->vp_index = vha->vp_idx; + + h = qlt_make_handle(vha); + if (unlikely(h == QLA_TGT_NULL_HANDLE)) { + /* + * CTIO type 7 from the firmware doesn't provide a way to + * know the initiator's LOOP ID, hence we can't find + * the session and, so, the command. + */ + return -EAGAIN; + } else + ha->tgt.cmds[h-1] = prm->cmd; + + pkt->handle = h | CTIO_COMPLETION_HANDLE_MARK; + pkt->nport_handle = prm->cmd->loop_id; + pkt->timeout = __constant_cpu_to_le16(QLA_TGT_TIMEOUT); + pkt->initiator_id[0] = atio->u.isp24.fcp_hdr.s_id[2]; + pkt->initiator_id[1] = atio->u.isp24.fcp_hdr.s_id[1]; + pkt->initiator_id[2] = atio->u.isp24.fcp_hdr.s_id[0]; + pkt->exchange_addr = atio->u.isp24.exchange_addr; + pkt->u.status0.flags |= (atio->u.isp24.attr << 9); + pkt->u.status0.ox_id = swab16(atio->u.isp24.fcp_hdr.ox_id); + pkt->u.status0.relative_offset = cpu_to_le32(prm->cmd->offset); + + ql_dbg(ql_dbg_tgt, vha, 0xe00c, + "qla_target(%d): handle(cmd) -> %08x, timeout %d, ox_id %#x\n", + vha->vp_idx, pkt->handle, QLA_TGT_TIMEOUT, + le16_to_cpu(pkt->u.status0.ox_id)); + return 0; +} + +/* + * ha->hardware_lock supposed to be held on entry. We have already made sure + * that there is sufficient amount of request entries to not drop it. + */ +static void qlt_load_cont_data_segments(struct qla_tgt_prm *prm, + struct scsi_qla_host *vha) +{ + int cnt; + uint32_t *dword_ptr; + int enable_64bit_addressing = prm->tgt->tgt_enable_64bit_addr; + + /* Build continuation packets */ + while (prm->seg_cnt > 0) { + cont_a64_entry_t *cont_pkt64 = + (cont_a64_entry_t *)qlt_get_req_pkt(vha); + + /* + * Make sure that from cont_pkt64 none of + * 64-bit specific fields used for 32-bit + * addressing. Cast to (cont_entry_t *) for + * that. + */ + + memset(cont_pkt64, 0, sizeof(*cont_pkt64)); + + cont_pkt64->entry_count = 1; + cont_pkt64->sys_define = 0; + + if (enable_64bit_addressing) { + cont_pkt64->entry_type = CONTINUE_A64_TYPE; + dword_ptr = + (uint32_t *)&cont_pkt64->dseg_0_address; + } else { + cont_pkt64->entry_type = CONTINUE_TYPE; + dword_ptr = + (uint32_t *)&((cont_entry_t *) + cont_pkt64)->dseg_0_address; + } + + /* Load continuation entry data segments */ + for (cnt = 0; + cnt < prm->tgt->datasegs_per_cont && prm->seg_cnt; + cnt++, prm->seg_cnt--) { + *dword_ptr++ = + cpu_to_le32(pci_dma_lo32 + (sg_dma_address(prm->sg))); + if (enable_64bit_addressing) { + *dword_ptr++ = + cpu_to_le32(pci_dma_hi32 + (sg_dma_address + (prm->sg))); + } + *dword_ptr++ = cpu_to_le32(sg_dma_len(prm->sg)); + + ql_dbg(ql_dbg_tgt, vha, 0xe00d, + "S/G Segment Cont. phys_addr=%llx:%llx, len=%d\n", + (long long unsigned int) + pci_dma_hi32(sg_dma_address(prm->sg)), + (long long unsigned int) + pci_dma_lo32(sg_dma_address(prm->sg)), + (int)sg_dma_len(prm->sg)); + + prm->sg = sg_next(prm->sg); + } + } +} + +/* + * ha->hardware_lock supposed to be held on entry. We have already made sure + * that there is sufficient amount of request entries to not drop it. + */ +static void qlt_load_data_segments(struct qla_tgt_prm *prm, + struct scsi_qla_host *vha) +{ + int cnt; + uint32_t *dword_ptr; + int enable_64bit_addressing = prm->tgt->tgt_enable_64bit_addr; + struct ctio7_to_24xx *pkt24 = (struct ctio7_to_24xx *)prm->pkt; + + ql_dbg(ql_dbg_tgt, vha, 0xe00e, + "iocb->scsi_status=%x, iocb->flags=%x\n", + le16_to_cpu(pkt24->u.status0.scsi_status), + le16_to_cpu(pkt24->u.status0.flags)); + + pkt24->u.status0.transfer_length = cpu_to_le32(prm->cmd->bufflen); + + /* Setup packet address segment pointer */ + dword_ptr = pkt24->u.status0.dseg_0_address; + + /* Set total data segment count */ + if (prm->seg_cnt) + pkt24->dseg_count = cpu_to_le16(prm->seg_cnt); + + if (prm->seg_cnt == 0) { + /* No data transfer */ + *dword_ptr++ = 0; + *dword_ptr = 0; + return; + } + + /* If scatter gather */ + ql_dbg(ql_dbg_tgt, vha, 0xe00f, "%s", "Building S/G data segments..."); + + /* Load command entry data segments */ + for (cnt = 0; + (cnt < prm->tgt->datasegs_per_cmd) && prm->seg_cnt; + cnt++, prm->seg_cnt--) { + *dword_ptr++ = + cpu_to_le32(pci_dma_lo32(sg_dma_address(prm->sg))); + if (enable_64bit_addressing) { + *dword_ptr++ = + cpu_to_le32(pci_dma_hi32( + sg_dma_address(prm->sg))); + } + *dword_ptr++ = cpu_to_le32(sg_dma_len(prm->sg)); + + ql_dbg(ql_dbg_tgt, vha, 0xe010, + "S/G Segment phys_addr=%llx:%llx, len=%d\n", + (long long unsigned int)pci_dma_hi32(sg_dma_address( + prm->sg)), + (long long unsigned int)pci_dma_lo32(sg_dma_address( + prm->sg)), + (int)sg_dma_len(prm->sg)); + + prm->sg = sg_next(prm->sg); + } + + qlt_load_cont_data_segments(prm, vha); +} + +static inline int qlt_has_data(struct qla_tgt_cmd *cmd) +{ + return cmd->bufflen > 0; +} + +/* + * Called without ha->hardware_lock held + */ +static int qlt_pre_xmit_response(struct qla_tgt_cmd *cmd, + struct qla_tgt_prm *prm, int xmit_type, uint8_t scsi_status, + uint32_t *full_req_cnt) +{ + struct qla_tgt *tgt = cmd->tgt; + struct scsi_qla_host *vha = tgt->vha; + struct qla_hw_data *ha = vha->hw; + struct se_cmd *se_cmd = &cmd->se_cmd; + + if (unlikely(cmd->aborted)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf014, + "qla_target(%d): terminating exchange " + "for aborted cmd=%p (se_cmd=%p, tag=%d)", vha->vp_idx, cmd, + se_cmd, cmd->tag); + + cmd->state = QLA_TGT_STATE_ABORTED; + + qlt_send_term_exchange(vha, cmd, &cmd->atio, 0); + + /* !! At this point cmd could be already freed !! */ + return QLA_TGT_PRE_XMIT_RESP_CMD_ABORTED; + } + + ql_dbg(ql_dbg_tgt, vha, 0xe011, "qla_target(%d): tag=%u\n", + vha->vp_idx, cmd->tag); + + prm->cmd = cmd; + prm->tgt = tgt; + prm->rq_result = scsi_status; + prm->sense_buffer = &cmd->sense_buffer[0]; + prm->sense_buffer_len = TRANSPORT_SENSE_BUFFER; + prm->sg = NULL; + prm->seg_cnt = -1; + prm->req_cnt = 1; + prm->add_status_pkt = 0; + + ql_dbg(ql_dbg_tgt, vha, 0xe012, "rq_result=%x, xmit_type=%x\n", + prm->rq_result, xmit_type); + + /* Send marker if required */ + if (qlt_issue_marker(vha, 0) != QLA_SUCCESS) + return -EFAULT; + + ql_dbg(ql_dbg_tgt, vha, 0xe013, "CTIO start: vha(%d)\n", vha->vp_idx); + + if ((xmit_type & QLA_TGT_XMIT_DATA) && qlt_has_data(cmd)) { + if (qlt_pci_map_calc_cnt(prm) != 0) + return -EAGAIN; + } + + *full_req_cnt = prm->req_cnt; + + if (se_cmd->se_cmd_flags & SCF_UNDERFLOW_BIT) { + prm->residual = se_cmd->residual_count; + ql_dbg(ql_dbg_tgt, vha, 0xe014, + "Residual underflow: %d (tag %d, " + "op %x, bufflen %d, rq_result %x)\n", prm->residual, + cmd->tag, se_cmd->t_task_cdb ? se_cmd->t_task_cdb[0] : 0, + cmd->bufflen, prm->rq_result); + prm->rq_result |= SS_RESIDUAL_UNDER; + } else if (se_cmd->se_cmd_flags & SCF_OVERFLOW_BIT) { + prm->residual = se_cmd->residual_count; + ql_dbg(ql_dbg_tgt, vha, 0xe015, + "Residual overflow: %d (tag %d, " + "op %x, bufflen %d, rq_result %x)\n", prm->residual, + cmd->tag, se_cmd->t_task_cdb ? se_cmd->t_task_cdb[0] : 0, + cmd->bufflen, prm->rq_result); + prm->rq_result |= SS_RESIDUAL_OVER; + } + + if (xmit_type & QLA_TGT_XMIT_STATUS) { + /* + * If QLA_TGT_XMIT_DATA is not set, add_status_pkt will be + * ignored in *xmit_response() below + */ + if (qlt_has_data(cmd)) { + if (QLA_TGT_SENSE_VALID(prm->sense_buffer) || + (IS_FWI2_CAPABLE(ha) && + (prm->rq_result != 0))) { + prm->add_status_pkt = 1; + (*full_req_cnt)++; + } + } + } + + ql_dbg(ql_dbg_tgt, vha, 0xe016, + "req_cnt=%d, full_req_cnt=%d, add_status_pkt=%d\n", + prm->req_cnt, *full_req_cnt, prm->add_status_pkt); + + return 0; +} + +static inline int qlt_need_explicit_conf(struct qla_hw_data *ha, + struct qla_tgt_cmd *cmd, int sending_sense) +{ + if (ha->tgt.enable_class_2) + return 0; + + if (sending_sense) + return cmd->conf_compl_supported; + else + return ha->tgt.enable_explicit_conf && + cmd->conf_compl_supported; +} + +#ifdef CONFIG_QLA_TGT_DEBUG_SRR +/* + * Original taken from the XFS code + */ +static unsigned long qlt_srr_random(void) +{ + static int Inited; + static unsigned long RandomValue; + static DEFINE_SPINLOCK(lock); + /* cycles pseudo-randomly through all values between 1 and 2^31 - 2 */ + register long rv; + register long lo; + register long hi; + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + if (!Inited) { + RandomValue = jiffies; + Inited = 1; + } + rv = RandomValue; + hi = rv / 127773; + lo = rv % 127773; + rv = 16807 * lo - 2836 * hi; + if (rv <= 0) + rv += 2147483647; + RandomValue = rv; + spin_unlock_irqrestore(&lock, flags); + return rv; +} + +static void qlt_check_srr_debug(struct qla_tgt_cmd *cmd, int *xmit_type) +{ +#if 0 /* This is not a real status packets lost, so it won't lead to SRR */ + if ((*xmit_type & QLA_TGT_XMIT_STATUS) && (qlt_srr_random() % 200) + == 50) { + *xmit_type &= ~QLA_TGT_XMIT_STATUS; + ql_dbg(ql_dbg_tgt_mgt, cmd->vha, 0xf015, + "Dropping cmd %p (tag %d) status", cmd, cmd->tag); + } +#endif + /* + * It's currently not possible to simulate SRRs for FCP_WRITE without + * a physical link layer failure, so don't even try here.. + */ + if (cmd->dma_data_direction != DMA_FROM_DEVICE) + return; + + if (qlt_has_data(cmd) && (cmd->sg_cnt > 1) && + ((qlt_srr_random() % 100) == 20)) { + int i, leave = 0; + unsigned int tot_len = 0; + + while (leave == 0) + leave = qlt_srr_random() % cmd->sg_cnt; + + for (i = 0; i < leave; i++) + tot_len += cmd->sg[i].length; + + ql_dbg(ql_dbg_tgt_mgt, cmd->vha, 0xf016, + "Cutting cmd %p (tag %d) buffer" + " tail to len %d, sg_cnt %d (cmd->bufflen %d," + " cmd->sg_cnt %d)", cmd, cmd->tag, tot_len, leave, + cmd->bufflen, cmd->sg_cnt); + + cmd->bufflen = tot_len; + cmd->sg_cnt = leave; + } + + if (qlt_has_data(cmd) && ((qlt_srr_random() % 100) == 70)) { + unsigned int offset = qlt_srr_random() % cmd->bufflen; + + ql_dbg(ql_dbg_tgt_mgt, cmd->vha, 0xf017, + "Cutting cmd %p (tag %d) buffer head " + "to offset %d (cmd->bufflen %d)", cmd, cmd->tag, offset, + cmd->bufflen); + if (offset == 0) + *xmit_type &= ~QLA_TGT_XMIT_DATA; + else if (qlt_set_data_offset(cmd, offset)) { + ql_dbg(ql_dbg_tgt_mgt, cmd->vha, 0xf018, + "qlt_set_data_offset() failed (tag %d)", cmd->tag); + } + } +} +#else +static inline void qlt_check_srr_debug(struct qla_tgt_cmd *cmd, int *xmit_type) +{} +#endif + +static void qlt_24xx_init_ctio_to_isp(struct ctio7_to_24xx *ctio, + struct qla_tgt_prm *prm) +{ + prm->sense_buffer_len = min_t(uint32_t, prm->sense_buffer_len, + (uint32_t)sizeof(ctio->u.status1.sense_data)); + ctio->u.status0.flags |= + __constant_cpu_to_le16(CTIO7_FLAGS_SEND_STATUS); + if (qlt_need_explicit_conf(prm->tgt->ha, prm->cmd, 0)) { + ctio->u.status0.flags |= __constant_cpu_to_le16( + CTIO7_FLAGS_EXPLICIT_CONFORM | + CTIO7_FLAGS_CONFORM_REQ); + } + ctio->u.status0.residual = cpu_to_le32(prm->residual); + ctio->u.status0.scsi_status = cpu_to_le16(prm->rq_result); + if (QLA_TGT_SENSE_VALID(prm->sense_buffer)) { + int i; + + if (qlt_need_explicit_conf(prm->tgt->ha, prm->cmd, 1)) { + if (prm->cmd->se_cmd.scsi_status != 0) { + ql_dbg(ql_dbg_tgt, prm->cmd->vha, 0xe017, + "Skipping EXPLICIT_CONFORM and " + "CTIO7_FLAGS_CONFORM_REQ for FCP READ w/ " + "non GOOD status\n"); + goto skip_explict_conf; + } + ctio->u.status1.flags |= __constant_cpu_to_le16( + CTIO7_FLAGS_EXPLICIT_CONFORM | + CTIO7_FLAGS_CONFORM_REQ); + } +skip_explict_conf: + ctio->u.status1.flags &= + ~__constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_0); + ctio->u.status1.flags |= + __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1); + ctio->u.status1.scsi_status |= + __constant_cpu_to_le16(SS_SENSE_LEN_VALID); + ctio->u.status1.sense_length = + cpu_to_le16(prm->sense_buffer_len); + for (i = 0; i < prm->sense_buffer_len/4; i++) + ((uint32_t *)ctio->u.status1.sense_data)[i] = + cpu_to_be32(((uint32_t *)prm->sense_buffer)[i]); +#if 0 + if (unlikely((prm->sense_buffer_len % 4) != 0)) { + static int q; + if (q < 10) { + ql_dbg(ql_dbg_tgt, vha, 0xe04f, + "qla_target(%d): %d bytes of sense " + "lost", prm->tgt->ha->vp_idx, + prm->sense_buffer_len % 4); + q++; + } + } +#endif + } else { + ctio->u.status1.flags &= + ~__constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_0); + ctio->u.status1.flags |= + __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1); + ctio->u.status1.sense_length = 0; + memset(ctio->u.status1.sense_data, 0, + sizeof(ctio->u.status1.sense_data)); + } + + /* Sense with len > 24, is it possible ??? */ +} + +/* + * Callback to setup response of xmit_type of QLA_TGT_XMIT_DATA and * + * QLA_TGT_XMIT_STATUS for >= 24xx silicon + */ +int qlt_xmit_response(struct qla_tgt_cmd *cmd, int xmit_type, + uint8_t scsi_status) +{ + struct scsi_qla_host *vha = cmd->vha; + struct qla_hw_data *ha = vha->hw; + struct ctio7_to_24xx *pkt; + struct qla_tgt_prm prm; + uint32_t full_req_cnt = 0; + unsigned long flags = 0; + int res; + + memset(&prm, 0, sizeof(prm)); + qlt_check_srr_debug(cmd, &xmit_type); + + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe018, + "is_send_status=%d, cmd->bufflen=%d, cmd->sg_cnt=%d, " + "cmd->dma_data_direction=%d\n", (xmit_type & QLA_TGT_XMIT_STATUS) ? + 1 : 0, cmd->bufflen, cmd->sg_cnt, cmd->dma_data_direction); + + res = qlt_pre_xmit_response(cmd, &prm, xmit_type, scsi_status, + &full_req_cnt); + if (unlikely(res != 0)) { + if (res == QLA_TGT_PRE_XMIT_RESP_CMD_ABORTED) + return 0; + + return res; + } + + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Does F/W have an IOCBs for this request */ + res = qlt_check_reserve_free_req(vha, full_req_cnt); + if (unlikely(res)) + goto out_unmap_unlock; + + res = qlt_24xx_build_ctio_pkt(&prm, vha); + if (unlikely(res != 0)) + goto out_unmap_unlock; + + + pkt = (struct ctio7_to_24xx *)prm.pkt; + + if (qlt_has_data(cmd) && (xmit_type & QLA_TGT_XMIT_DATA)) { + pkt->u.status0.flags |= + __constant_cpu_to_le16(CTIO7_FLAGS_DATA_IN | + CTIO7_FLAGS_STATUS_MODE_0); + + qlt_load_data_segments(&prm, vha); + + if (prm.add_status_pkt == 0) { + if (xmit_type & QLA_TGT_XMIT_STATUS) { + pkt->u.status0.scsi_status = + cpu_to_le16(prm.rq_result); + pkt->u.status0.residual = + cpu_to_le32(prm.residual); + pkt->u.status0.flags |= __constant_cpu_to_le16( + CTIO7_FLAGS_SEND_STATUS); + if (qlt_need_explicit_conf(ha, cmd, 0)) { + pkt->u.status0.flags |= + __constant_cpu_to_le16( + CTIO7_FLAGS_EXPLICIT_CONFORM | + CTIO7_FLAGS_CONFORM_REQ); + } + } + + } else { + /* + * We have already made sure that there is sufficient + * amount of request entries to not drop HW lock in + * req_pkt(). + */ + struct ctio7_to_24xx *ctio = + (struct ctio7_to_24xx *)qlt_get_req_pkt(vha); + + ql_dbg(ql_dbg_tgt, vha, 0xe019, + "Building additional status packet\n"); + + memcpy(ctio, pkt, sizeof(*ctio)); + ctio->entry_count = 1; + ctio->dseg_count = 0; + ctio->u.status1.flags &= ~__constant_cpu_to_le16( + CTIO7_FLAGS_DATA_IN); + + /* Real finish is ctio_m1's finish */ + pkt->handle |= CTIO_INTERMEDIATE_HANDLE_MARK; + pkt->u.status0.flags |= __constant_cpu_to_le16( + CTIO7_FLAGS_DONT_RET_CTIO); + qlt_24xx_init_ctio_to_isp((struct ctio7_to_24xx *)ctio, + &prm); + pr_debug("Status CTIO7: %p\n", ctio); + } + } else + qlt_24xx_init_ctio_to_isp(pkt, &prm); + + + cmd->state = QLA_TGT_STATE_PROCESSED; /* Mid-level is done processing */ + + ql_dbg(ql_dbg_tgt, vha, 0xe01a, + "Xmitting CTIO7 response pkt for 24xx: %p scsi_status: 0x%02x\n", + pkt, scsi_status); + + qla2x00_start_iocbs(vha, vha->req); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return 0; + +out_unmap_unlock: + if (cmd->sg_mapped) + qlt_unmap_sg(vha, cmd); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return res; +} +EXPORT_SYMBOL(qlt_xmit_response); + +int qlt_rdy_to_xfer(struct qla_tgt_cmd *cmd) +{ + struct ctio7_to_24xx *pkt; + struct scsi_qla_host *vha = cmd->vha; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = cmd->tgt; + struct qla_tgt_prm prm; + unsigned long flags; + int res = 0; + + memset(&prm, 0, sizeof(prm)); + prm.cmd = cmd; + prm.tgt = tgt; + prm.sg = NULL; + prm.req_cnt = 1; + + /* Send marker if required */ + if (qlt_issue_marker(vha, 0) != QLA_SUCCESS) + return -EIO; + + ql_dbg(ql_dbg_tgt, vha, 0xe01b, "CTIO_start: vha(%d)", + (int)vha->vp_idx); + + /* Calculate number of entries and segments required */ + if (qlt_pci_map_calc_cnt(&prm) != 0) + return -EAGAIN; + + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Does F/W have an IOCBs for this request */ + res = qlt_check_reserve_free_req(vha, prm.req_cnt); + if (res != 0) + goto out_unlock_free_unmap; + + res = qlt_24xx_build_ctio_pkt(&prm, vha); + if (unlikely(res != 0)) + goto out_unlock_free_unmap; + pkt = (struct ctio7_to_24xx *)prm.pkt; + pkt->u.status0.flags |= __constant_cpu_to_le16(CTIO7_FLAGS_DATA_OUT | + CTIO7_FLAGS_STATUS_MODE_0); + qlt_load_data_segments(&prm, vha); + + cmd->state = QLA_TGT_STATE_NEED_DATA; + + qla2x00_start_iocbs(vha, vha->req); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return res; + +out_unlock_free_unmap: + if (cmd->sg_mapped) + qlt_unmap_sg(vha, cmd); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return res; +} +EXPORT_SYMBOL(qlt_rdy_to_xfer); + +/* If hardware_lock held on entry, might drop it, then reaquire */ +/* This function sends the appropriate CTIO to ISP 2xxx or 24xx */ +static int __qlt_send_term_exchange(struct scsi_qla_host *vha, + struct qla_tgt_cmd *cmd, + struct atio_from_isp *atio) +{ + struct ctio7_to_24xx *ctio24; + struct qla_hw_data *ha = vha->hw; + request_t *pkt; + int ret = 0; + + ql_dbg(ql_dbg_tgt, vha, 0xe01c, "Sending TERM EXCH CTIO (ha=%p)\n", ha); + + pkt = (request_t *)qla2x00_alloc_iocbs(vha, NULL); + if (pkt == NULL) { + ql_dbg(ql_dbg_tgt, vha, 0xe050, + "qla_target(%d): %s failed: unable to allocate " + "request packet\n", vha->vp_idx, __func__); + return -ENOMEM; + } + + if (cmd != NULL) { + if (cmd->state < QLA_TGT_STATE_PROCESSED) { + ql_dbg(ql_dbg_tgt, vha, 0xe051, + "qla_target(%d): Terminating cmd %p with " + "incorrect state %d\n", vha->vp_idx, cmd, + cmd->state); + } else + ret = 1; + } + + pkt->entry_count = 1; + pkt->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK; + + ctio24 = (struct ctio7_to_24xx *)pkt; + ctio24->entry_type = CTIO_TYPE7; + ctio24->nport_handle = cmd ? cmd->loop_id : CTIO7_NHANDLE_UNRECOGNIZED; + ctio24->timeout = __constant_cpu_to_le16(QLA_TGT_TIMEOUT); + ctio24->vp_index = vha->vp_idx; + ctio24->initiator_id[0] = atio->u.isp24.fcp_hdr.s_id[2]; + ctio24->initiator_id[1] = atio->u.isp24.fcp_hdr.s_id[1]; + ctio24->initiator_id[2] = atio->u.isp24.fcp_hdr.s_id[0]; + ctio24->exchange_addr = atio->u.isp24.exchange_addr; + ctio24->u.status1.flags = (atio->u.isp24.attr << 9) | + __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1 | + CTIO7_FLAGS_TERMINATE); + ctio24->u.status1.ox_id = swab16(atio->u.isp24.fcp_hdr.ox_id); + + /* Most likely, it isn't needed */ + ctio24->u.status1.residual = get_unaligned((uint32_t *) + &atio->u.isp24.fcp_cmnd.add_cdb[ + atio->u.isp24.fcp_cmnd.add_cdb_len]); + if (ctio24->u.status1.residual != 0) + ctio24->u.status1.scsi_status |= SS_RESIDUAL_UNDER; + + qla2x00_start_iocbs(vha, vha->req); + return ret; +} + +static void qlt_send_term_exchange(struct scsi_qla_host *vha, + struct qla_tgt_cmd *cmd, struct atio_from_isp *atio, int ha_locked) +{ + unsigned long flags; + int rc; + + if (qlt_issue_marker(vha, ha_locked) < 0) + return; + + if (ha_locked) { + rc = __qlt_send_term_exchange(vha, cmd, atio); + goto done; + } + spin_lock_irqsave(&vha->hw->hardware_lock, flags); + rc = __qlt_send_term_exchange(vha, cmd, atio); + spin_unlock_irqrestore(&vha->hw->hardware_lock, flags); +done: + if (rc == 1) { + if (!ha_locked && !in_interrupt()) + msleep(250); /* just in case */ + + vha->hw->tgt.tgt_ops->free_cmd(cmd); + } +} + +void qlt_free_cmd(struct qla_tgt_cmd *cmd) +{ + BUG_ON(cmd->sg_mapped); + + if (unlikely(cmd->free_sg)) + kfree(cmd->sg); + kmem_cache_free(qla_tgt_cmd_cachep, cmd); +} +EXPORT_SYMBOL(qlt_free_cmd); + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_prepare_srr_ctio(struct scsi_qla_host *vha, + struct qla_tgt_cmd *cmd, void *ctio) +{ + struct qla_tgt_srr_ctio *sc; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + struct qla_tgt_srr_imm *imm; + + tgt->ctio_srr_id++; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf019, + "qla_target(%d): CTIO with SRR status received\n", vha->vp_idx); + + if (!ctio) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf055, + "qla_target(%d): SRR CTIO, but ctio is NULL\n", + vha->vp_idx); + return -EINVAL; + } + + sc = kzalloc(sizeof(*sc), GFP_ATOMIC); + if (sc != NULL) { + sc->cmd = cmd; + /* IRQ is already OFF */ + spin_lock(&tgt->srr_lock); + sc->srr_id = tgt->ctio_srr_id; + list_add_tail(&sc->srr_list_entry, + &tgt->srr_ctio_list); + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01a, + "CTIO SRR %p added (id %d)\n", sc, sc->srr_id); + if (tgt->imm_srr_id == tgt->ctio_srr_id) { + int found = 0; + list_for_each_entry(imm, &tgt->srr_imm_list, + srr_list_entry) { + if (imm->srr_id == sc->srr_id) { + found = 1; + break; + } + } + if (found) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01b, + "Scheduling srr work\n"); + schedule_work(&tgt->srr_work); + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf056, + "qla_target(%d): imm_srr_id " + "== ctio_srr_id (%d), but there is no " + "corresponding SRR IMM, deleting CTIO " + "SRR %p\n", vha->vp_idx, + tgt->ctio_srr_id, sc); + list_del(&sc->srr_list_entry); + spin_unlock(&tgt->srr_lock); + + kfree(sc); + return -EINVAL; + } + } + spin_unlock(&tgt->srr_lock); + } else { + struct qla_tgt_srr_imm *ti; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf057, + "qla_target(%d): Unable to allocate SRR CTIO entry\n", + vha->vp_idx); + spin_lock(&tgt->srr_lock); + list_for_each_entry_safe(imm, ti, &tgt->srr_imm_list, + srr_list_entry) { + if (imm->srr_id == tgt->ctio_srr_id) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01c, + "IMM SRR %p deleted (id %d)\n", + imm, imm->srr_id); + list_del(&imm->srr_list_entry); + qlt_reject_free_srr_imm(vha, imm, 1); + } + } + spin_unlock(&tgt->srr_lock); + + return -ENOMEM; + } + + return 0; +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static int qlt_term_ctio_exchange(struct scsi_qla_host *vha, void *ctio, + struct qla_tgt_cmd *cmd, uint32_t status) +{ + int term = 0; + + if (ctio != NULL) { + struct ctio7_from_24xx *c = (struct ctio7_from_24xx *)ctio; + term = !(c->flags & + __constant_cpu_to_le16(OF_TERM_EXCH)); + } else + term = 1; + + if (term) + qlt_send_term_exchange(vha, cmd, &cmd->atio, 1); + + return term; +} + +/* ha->hardware_lock supposed to be held on entry */ +static inline struct qla_tgt_cmd *qlt_get_cmd(struct scsi_qla_host *vha, + uint32_t handle) +{ + struct qla_hw_data *ha = vha->hw; + + handle--; + if (ha->tgt.cmds[handle] != NULL) { + struct qla_tgt_cmd *cmd = ha->tgt.cmds[handle]; + ha->tgt.cmds[handle] = NULL; + return cmd; + } else + return NULL; +} + +/* ha->hardware_lock supposed to be held on entry */ +static struct qla_tgt_cmd *qlt_ctio_to_cmd(struct scsi_qla_host *vha, + uint32_t handle, void *ctio) +{ + struct qla_tgt_cmd *cmd = NULL; + + /* Clear out internal marks */ + handle &= ~(CTIO_COMPLETION_HANDLE_MARK | + CTIO_INTERMEDIATE_HANDLE_MARK); + + if (handle != QLA_TGT_NULL_HANDLE) { + if (unlikely(handle == QLA_TGT_SKIP_HANDLE)) { + ql_dbg(ql_dbg_tgt, vha, 0xe01d, "%s", + "SKIP_HANDLE CTIO\n"); + return NULL; + } + /* handle-1 is actually used */ + if (unlikely(handle > MAX_OUTSTANDING_COMMANDS)) { + ql_dbg(ql_dbg_tgt, vha, 0xe052, + "qla_target(%d): Wrong handle %x received\n", + vha->vp_idx, handle); + return NULL; + } + cmd = qlt_get_cmd(vha, handle); + if (unlikely(cmd == NULL)) { + ql_dbg(ql_dbg_tgt, vha, 0xe053, + "qla_target(%d): Suspicious: unable to " + "find the command with handle %x\n", vha->vp_idx, + handle); + return NULL; + } + } else if (ctio != NULL) { + /* We can't get loop ID from CTIO7 */ + ql_dbg(ql_dbg_tgt, vha, 0xe054, + "qla_target(%d): Wrong CTIO received: QLA24xx doesn't " + "support NULL handles\n", vha->vp_idx); + return NULL; + } + + return cmd; +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_do_ctio_completion(struct scsi_qla_host *vha, uint32_t handle, + uint32_t status, void *ctio) +{ + struct qla_hw_data *ha = vha->hw; + struct se_cmd *se_cmd; + struct target_core_fabric_ops *tfo; + struct qla_tgt_cmd *cmd; + + ql_dbg(ql_dbg_tgt, vha, 0xe01e, + "qla_target(%d): handle(ctio %p status %#x) <- %08x\n", + vha->vp_idx, ctio, status, handle); + + if (handle & CTIO_INTERMEDIATE_HANDLE_MARK) { + /* That could happen only in case of an error/reset/abort */ + if (status != CTIO_SUCCESS) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01d, + "Intermediate CTIO received" + " (status %x)\n", status); + } + return; + } + + cmd = qlt_ctio_to_cmd(vha, handle, ctio); + if (cmd == NULL) { + if (status != CTIO_SUCCESS) + qlt_term_ctio_exchange(vha, ctio, NULL, status); + return; + } + se_cmd = &cmd->se_cmd; + tfo = se_cmd->se_tfo; + + if (cmd->sg_mapped) + qlt_unmap_sg(vha, cmd); + + if (unlikely(status != CTIO_SUCCESS)) { + switch (status & 0xFFFF) { + case CTIO_LIP_RESET: + case CTIO_TARGET_RESET: + case CTIO_ABORTED: + case CTIO_TIMEOUT: + case CTIO_INVALID_RX_ID: + /* They are OK */ + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf058, + "qla_target(%d): CTIO with " + "status %#x received, state %x, se_cmd %p, " + "(LIP_RESET=e, ABORTED=2, TARGET_RESET=17, " + "TIMEOUT=b, INVALID_RX_ID=8)\n", vha->vp_idx, + status, cmd->state, se_cmd); + break; + + case CTIO_PORT_LOGGED_OUT: + case CTIO_PORT_UNAVAILABLE: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf059, + "qla_target(%d): CTIO with PORT LOGGED " + "OUT (29) or PORT UNAVAILABLE (28) status %x " + "received (state %x, se_cmd %p)\n", vha->vp_idx, + status, cmd->state, se_cmd); + break; + + case CTIO_SRR_RECEIVED: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05a, + "qla_target(%d): CTIO with SRR_RECEIVED" + " status %x received (state %x, se_cmd %p)\n", + vha->vp_idx, status, cmd->state, se_cmd); + if (qlt_prepare_srr_ctio(vha, cmd, ctio) != 0) + break; + else + return; + + default: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05b, + "qla_target(%d): CTIO with error status " + "0x%x received (state %x, se_cmd %p\n", + vha->vp_idx, status, cmd->state, se_cmd); + break; + } + + if (cmd->state != QLA_TGT_STATE_NEED_DATA) + if (qlt_term_ctio_exchange(vha, ctio, cmd, status)) + return; + } + + if (cmd->state == QLA_TGT_STATE_PROCESSED) { + ql_dbg(ql_dbg_tgt, vha, 0xe01f, "Command %p finished\n", cmd); + } else if (cmd->state == QLA_TGT_STATE_NEED_DATA) { + int rx_status = 0; + + cmd->state = QLA_TGT_STATE_DATA_IN; + + if (unlikely(status != CTIO_SUCCESS)) + rx_status = -EIO; + else + cmd->write_data_transferred = 1; + + ql_dbg(ql_dbg_tgt, vha, 0xe020, + "Data received, context %x, rx_status %d\n", + 0x0, rx_status); + + ha->tgt.tgt_ops->handle_data(cmd); + return; + } else if (cmd->state == QLA_TGT_STATE_ABORTED) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01e, + "Aborted command %p (tag %d) finished\n", cmd, cmd->tag); + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05c, + "qla_target(%d): A command in state (%d) should " + "not return a CTIO complete\n", vha->vp_idx, cmd->state); + } + + if (unlikely(status != CTIO_SUCCESS)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01f, "Finishing failed CTIO\n"); + dump_stack(); + } + + ha->tgt.tgt_ops->free_cmd(cmd); +} + +/* ha->hardware_lock supposed to be held on entry */ +/* called via callback from qla2xxx */ +void qlt_ctio_completion(struct scsi_qla_host *vha, uint32_t handle) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + + if (likely(tgt == NULL)) { + ql_dbg(ql_dbg_tgt, vha, 0xe021, + "CTIO, but target mode not enabled" + " (ha %d %p handle %#x)", vha->vp_idx, ha, handle); + return; + } + + tgt->irq_cmd_count++; + qlt_do_ctio_completion(vha, handle, CTIO_SUCCESS, NULL); + tgt->irq_cmd_count--; +} + +static inline int qlt_get_fcp_task_attr(struct scsi_qla_host *vha, + uint8_t task_codes) +{ + int fcp_task_attr; + + switch (task_codes) { + case ATIO_SIMPLE_QUEUE: + fcp_task_attr = MSG_SIMPLE_TAG; + break; + case ATIO_HEAD_OF_QUEUE: + fcp_task_attr = MSG_HEAD_TAG; + break; + case ATIO_ORDERED_QUEUE: + fcp_task_attr = MSG_ORDERED_TAG; + break; + case ATIO_ACA_QUEUE: + fcp_task_attr = MSG_ACA_TAG; + break; + case ATIO_UNTAGGED: + fcp_task_attr = MSG_SIMPLE_TAG; + break; + default: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05d, + "qla_target: unknown task code %x, use ORDERED instead\n", + task_codes); + fcp_task_attr = MSG_ORDERED_TAG; + break; + } + + return fcp_task_attr; +} + +static struct qla_tgt_sess *qlt_make_local_sess(struct scsi_qla_host *, + uint8_t *); +/* + * Process context for I/O path into tcm_qla2xxx code + */ +static void qlt_do_work(struct work_struct *work) +{ + struct qla_tgt_cmd *cmd = container_of(work, struct qla_tgt_cmd, work); + scsi_qla_host_t *vha = cmd->vha; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + struct qla_tgt_sess *sess = NULL; + struct atio_from_isp *atio = &cmd->atio; + unsigned char *cdb; + unsigned long flags; + uint32_t data_length; + int ret, fcp_task_attr, data_dir, bidi = 0; + + if (tgt->tgt_stop) + goto out_term; + + spin_lock_irqsave(&ha->hardware_lock, flags); + sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, + atio->u.isp24.fcp_hdr.s_id); + if (sess) { + if (unlikely(sess->tearing_down)) { + sess = NULL; + spin_unlock_irqrestore(&ha->hardware_lock, flags); + goto out_term; + } else { + /* + * Do the extra kref_get() before dropping + * qla_hw_data->hardware_lock. + */ + kref_get(&sess->se_sess->sess_kref); + } + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + if (unlikely(!sess)) { + uint8_t *s_id = atio->u.isp24.fcp_hdr.s_id; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf022, + "qla_target(%d): Unable to find wwn login" + " (s_id %x:%x:%x), trying to create it manually\n", + vha->vp_idx, s_id[0], s_id[1], s_id[2]); + + if (atio->u.raw.entry_count > 1) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf023, + "Dropping multy entry cmd %p\n", cmd); + goto out_term; + } + + mutex_lock(&ha->tgt.tgt_mutex); + sess = qlt_make_local_sess(vha, s_id); + /* sess has an extra creation ref. */ + mutex_unlock(&ha->tgt.tgt_mutex); + + if (!sess) + goto out_term; + } + + cmd->sess = sess; + cmd->loop_id = sess->loop_id; + cmd->conf_compl_supported = sess->conf_compl_supported; + + cdb = &atio->u.isp24.fcp_cmnd.cdb[0]; + cmd->tag = atio->u.isp24.exchange_addr; + cmd->unpacked_lun = scsilun_to_int( + (struct scsi_lun *)&atio->u.isp24.fcp_cmnd.lun); + + if (atio->u.isp24.fcp_cmnd.rddata && + atio->u.isp24.fcp_cmnd.wrdata) { + bidi = 1; + data_dir = DMA_TO_DEVICE; + } else if (atio->u.isp24.fcp_cmnd.rddata) + data_dir = DMA_FROM_DEVICE; + else if (atio->u.isp24.fcp_cmnd.wrdata) + data_dir = DMA_TO_DEVICE; + else + data_dir = DMA_NONE; + + fcp_task_attr = qlt_get_fcp_task_attr(vha, + atio->u.isp24.fcp_cmnd.task_attr); + data_length = be32_to_cpu(get_unaligned((uint32_t *) + &atio->u.isp24.fcp_cmnd.add_cdb[ + atio->u.isp24.fcp_cmnd.add_cdb_len])); + + ql_dbg(ql_dbg_tgt, vha, 0xe022, + "qla_target: START qla command: %p lun: 0x%04x (tag %d)\n", + cmd, cmd->unpacked_lun, cmd->tag); + + ret = vha->hw->tgt.tgt_ops->handle_cmd(vha, cmd, cdb, data_length, + fcp_task_attr, data_dir, bidi); + if (ret != 0) + goto out_term; + /* + * Drop extra session reference from qla_tgt_handle_cmd_for_atio*( + */ + ha->tgt.tgt_ops->put_sess(sess); + return; + +out_term: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf020, "Terminating work cmd %p", cmd); + /* + * cmd has not sent to target yet, so pass NULL as the second argument + */ + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_send_term_exchange(vha, NULL, &cmd->atio, 1); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + if (sess) + ha->tgt.tgt_ops->put_sess(sess); +} + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_handle_cmd_for_atio(struct scsi_qla_host *vha, + struct atio_from_isp *atio) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + struct qla_tgt_cmd *cmd; + + if (unlikely(tgt->tgt_stop)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf021, + "New command while device %p is shutting down\n", tgt); + return -EFAULT; + } + + cmd = kmem_cache_zalloc(qla_tgt_cmd_cachep, GFP_ATOMIC); + if (!cmd) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05e, + "qla_target(%d): Allocation of cmd failed\n", vha->vp_idx); + return -ENOMEM; + } + + INIT_LIST_HEAD(&cmd->cmd_list); + + memcpy(&cmd->atio, atio, sizeof(*atio)); + cmd->state = QLA_TGT_STATE_NEW; + cmd->tgt = ha->tgt.qla_tgt; + cmd->vha = vha; + + INIT_WORK(&cmd->work, qlt_do_work); + queue_work(qla_tgt_wq, &cmd->work); + return 0; + +} + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_issue_task_mgmt(struct qla_tgt_sess *sess, uint32_t lun, + int fn, void *iocb, int flags) +{ + struct scsi_qla_host *vha = sess->vha; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_mgmt_cmd *mcmd; + int res; + uint8_t tmr_func; + + mcmd = mempool_alloc(qla_tgt_mgmt_cmd_mempool, GFP_ATOMIC); + if (!mcmd) { + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10009, + "qla_target(%d): Allocation of management " + "command failed, some commands and their data could " + "leak\n", vha->vp_idx); + return -ENOMEM; + } + memset(mcmd, 0, sizeof(*mcmd)); + mcmd->sess = sess; + + if (iocb) { + memcpy(&mcmd->orig_iocb.imm_ntfy, iocb, + sizeof(mcmd->orig_iocb.imm_ntfy)); + } + mcmd->tmr_func = fn; + mcmd->flags = flags; + + switch (fn) { + case QLA_TGT_CLEAR_ACA: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10000, + "qla_target(%d): CLEAR_ACA received\n", sess->vha->vp_idx); + tmr_func = TMR_CLEAR_ACA; + break; + + case QLA_TGT_TARGET_RESET: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10001, + "qla_target(%d): TARGET_RESET received\n", + sess->vha->vp_idx); + tmr_func = TMR_TARGET_WARM_RESET; + break; + + case QLA_TGT_LUN_RESET: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10002, + "qla_target(%d): LUN_RESET received\n", sess->vha->vp_idx); + tmr_func = TMR_LUN_RESET; + break; + + case QLA_TGT_CLEAR_TS: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10003, + "qla_target(%d): CLEAR_TS received\n", sess->vha->vp_idx); + tmr_func = TMR_CLEAR_TASK_SET; + break; + + case QLA_TGT_ABORT_TS: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10004, + "qla_target(%d): ABORT_TS received\n", sess->vha->vp_idx); + tmr_func = TMR_ABORT_TASK_SET; + break; +#if 0 + case QLA_TGT_ABORT_ALL: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10005, + "qla_target(%d): Doing ABORT_ALL_TASKS\n", + sess->vha->vp_idx); + tmr_func = 0; + break; + + case QLA_TGT_ABORT_ALL_SESS: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10006, + "qla_target(%d): Doing ABORT_ALL_TASKS_SESS\n", + sess->vha->vp_idx); + tmr_func = 0; + break; + + case QLA_TGT_NEXUS_LOSS_SESS: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10007, + "qla_target(%d): Doing NEXUS_LOSS_SESS\n", + sess->vha->vp_idx); + tmr_func = 0; + break; + + case QLA_TGT_NEXUS_LOSS: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x10008, + "qla_target(%d): Doing NEXUS_LOSS\n", sess->vha->vp_idx); + tmr_func = 0; + break; +#endif + default: + ql_dbg(ql_dbg_tgt_tmr, vha, 0x1000a, + "qla_target(%d): Unknown task mgmt fn 0x%x\n", + sess->vha->vp_idx, fn); + mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool); + return -ENOSYS; + } + + res = ha->tgt.tgt_ops->handle_tmr(mcmd, lun, tmr_func, 0); + if (res != 0) { + ql_dbg(ql_dbg_tgt_tmr, vha, 0x1000b, + "qla_target(%d): tgt.tgt_ops->handle_tmr() failed: %d\n", + sess->vha->vp_idx, res); + mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool); + return -EFAULT; + } + + return 0; +} + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_handle_task_mgmt(struct scsi_qla_host *vha, void *iocb) +{ + struct atio_from_isp *a = (struct atio_from_isp *)iocb; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt; + struct qla_tgt_sess *sess; + uint32_t lun, unpacked_lun; + int lun_size, fn; + + tgt = ha->tgt.qla_tgt; + + lun = a->u.isp24.fcp_cmnd.lun; + lun_size = sizeof(a->u.isp24.fcp_cmnd.lun); + fn = a->u.isp24.fcp_cmnd.task_mgmt_flags; + sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, + a->u.isp24.fcp_hdr.s_id); + unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun); + + if (!sess) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf024, + "qla_target(%d): task mgmt fn 0x%x for " + "non-existant session\n", vha->vp_idx, fn); + return qlt_sched_sess_work(tgt, QLA_TGT_SESS_WORK_TM, iocb, + sizeof(struct atio_from_isp)); + } + + return qlt_issue_task_mgmt(sess, unpacked_lun, fn, iocb, 0); +} + +/* ha->hardware_lock supposed to be held on entry */ +static int __qlt_abort_task(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *iocb, struct qla_tgt_sess *sess) +{ + struct atio_from_isp *a = (struct atio_from_isp *)iocb; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_mgmt_cmd *mcmd; + uint32_t lun, unpacked_lun; + int rc; + + mcmd = mempool_alloc(qla_tgt_mgmt_cmd_mempool, GFP_ATOMIC); + if (mcmd == NULL) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05f, + "qla_target(%d): %s: Allocation of ABORT cmd failed\n", + vha->vp_idx, __func__); + return -ENOMEM; + } + memset(mcmd, 0, sizeof(*mcmd)); + + mcmd->sess = sess; + memcpy(&mcmd->orig_iocb.imm_ntfy, iocb, + sizeof(mcmd->orig_iocb.imm_ntfy)); + + lun = a->u.isp24.fcp_cmnd.lun; + unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun); + + rc = ha->tgt.tgt_ops->handle_tmr(mcmd, unpacked_lun, TMR_ABORT_TASK, + le16_to_cpu(iocb->u.isp2x.seq_id)); + if (rc != 0) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf060, + "qla_target(%d): tgt_ops->handle_tmr() failed: %d\n", + vha->vp_idx, rc); + mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool); + return -EFAULT; + } + + return 0; +} + +/* ha->hardware_lock supposed to be held on entry */ +static int qlt_abort_task(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *iocb) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess; + int loop_id; + + loop_id = GET_TARGET_ID(ha, (struct atio_from_isp *)iocb); + + sess = ha->tgt.tgt_ops->find_sess_by_loop_id(vha, loop_id); + if (sess == NULL) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf025, + "qla_target(%d): task abort for unexisting " + "session\n", vha->vp_idx); + return qlt_sched_sess_work(ha->tgt.qla_tgt, + QLA_TGT_SESS_WORK_ABORT, iocb, sizeof(*iocb)); + } + + return __qlt_abort_task(vha, iocb, sess); +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static int qlt_24xx_handle_els(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *iocb) +{ + struct qla_hw_data *ha = vha->hw; + int res = 0; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf026, + "qla_target(%d): Port ID: 0x%02x:%02x:%02x" + " ELS opcode: 0x%02x\n", vha->vp_idx, iocb->u.isp24.port_id[0], + iocb->u.isp24.port_id[1], iocb->u.isp24.port_id[2], + iocb->u.isp24.status_subcode); + + switch (iocb->u.isp24.status_subcode) { + case ELS_PLOGI: + case ELS_FLOGI: + case ELS_PRLI: + case ELS_LOGO: + case ELS_PRLO: + res = qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS_SESS); + break; + case ELS_PDISC: + case ELS_ADISC: + { + struct qla_tgt *tgt = ha->tgt.qla_tgt; + if (tgt->link_reinit_iocb_pending) { + qlt_send_notify_ack(vha, &tgt->link_reinit_iocb, + 0, 0, 0, 0, 0, 0); + tgt->link_reinit_iocb_pending = 0; + } + res = 1; /* send notify ack */ + break; + } + + default: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf061, + "qla_target(%d): Unsupported ELS command %x " + "received\n", vha->vp_idx, iocb->u.isp24.status_subcode); + res = qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS_SESS); + break; + } + + return res; +} + +static int qlt_set_data_offset(struct qla_tgt_cmd *cmd, uint32_t offset) +{ + struct scatterlist *sg, *sgp, *sg_srr, *sg_srr_start = NULL; + size_t first_offset = 0, rem_offset = offset, tmp = 0; + int i, sg_srr_cnt, bufflen = 0; + + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe023, + "Entering qla_tgt_set_data_offset: cmd: %p, cmd->sg: %p, " + "cmd->sg_cnt: %u, direction: %d\n", + cmd, cmd->sg, cmd->sg_cnt, cmd->dma_data_direction); + + /* + * FIXME: Reject non zero SRR relative offset until we can test + * this code properly. + */ + pr_debug("Rejecting non zero SRR rel_offs: %u\n", offset); + return -1; + + if (!cmd->sg || !cmd->sg_cnt) { + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe055, + "Missing cmd->sg or zero cmd->sg_cnt in" + " qla_tgt_set_data_offset\n"); + return -EINVAL; + } + /* + * Walk the current cmd->sg list until we locate the new sg_srr_start + */ + for_each_sg(cmd->sg, sg, cmd->sg_cnt, i) { + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe024, + "sg[%d]: %p page: %p, length: %d, offset: %d\n", + i, sg, sg_page(sg), sg->length, sg->offset); + + if ((sg->length + tmp) > offset) { + first_offset = rem_offset; + sg_srr_start = sg; + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe025, + "Found matching sg[%d], using %p as sg_srr_start, " + "and using first_offset: %zu\n", i, sg, + first_offset); + break; + } + tmp += sg->length; + rem_offset -= sg->length; + } + + if (!sg_srr_start) { + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe056, + "Unable to locate sg_srr_start for offset: %u\n", offset); + return -EINVAL; + } + sg_srr_cnt = (cmd->sg_cnt - i); + + sg_srr = kzalloc(sizeof(struct scatterlist) * sg_srr_cnt, GFP_KERNEL); + if (!sg_srr) { + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe057, + "Unable to allocate sgp\n"); + return -ENOMEM; + } + sg_init_table(sg_srr, sg_srr_cnt); + sgp = &sg_srr[0]; + /* + * Walk the remaining list for sg_srr_start, mapping to the newly + * allocated sg_srr taking first_offset into account. + */ + for_each_sg(sg_srr_start, sg, sg_srr_cnt, i) { + if (first_offset) { + sg_set_page(sgp, sg_page(sg), + (sg->length - first_offset), first_offset); + first_offset = 0; + } else { + sg_set_page(sgp, sg_page(sg), sg->length, 0); + } + bufflen += sgp->length; + + sgp = sg_next(sgp); + if (!sgp) + break; + } + + cmd->sg = sg_srr; + cmd->sg_cnt = sg_srr_cnt; + cmd->bufflen = bufflen; + cmd->offset += offset; + cmd->free_sg = 1; + + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe026, "New cmd->sg: %p\n", cmd->sg); + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe027, "New cmd->sg_cnt: %u\n", + cmd->sg_cnt); + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe028, "New cmd->bufflen: %u\n", + cmd->bufflen); + ql_dbg(ql_dbg_tgt, cmd->vha, 0xe029, "New cmd->offset: %u\n", + cmd->offset); + + if (cmd->sg_cnt < 0) + BUG(); + + if (cmd->bufflen < 0) + BUG(); + + return 0; +} + +static inline int qlt_srr_adjust_data(struct qla_tgt_cmd *cmd, + uint32_t srr_rel_offs, int *xmit_type) +{ + int res = 0, rel_offs; + + rel_offs = srr_rel_offs - cmd->offset; + ql_dbg(ql_dbg_tgt_mgt, cmd->vha, 0xf027, "srr_rel_offs=%d, rel_offs=%d", + srr_rel_offs, rel_offs); + + *xmit_type = QLA_TGT_XMIT_ALL; + + if (rel_offs < 0) { + ql_dbg(ql_dbg_tgt_mgt, cmd->vha, 0xf062, + "qla_target(%d): SRR rel_offs (%d) < 0", + cmd->vha->vp_idx, rel_offs); + res = -1; + } else if (rel_offs == cmd->bufflen) + *xmit_type = QLA_TGT_XMIT_STATUS; + else if (rel_offs > 0) + res = qlt_set_data_offset(cmd, rel_offs); + + return res; +} + +/* No locks, thread context */ +static void qlt_handle_srr(struct scsi_qla_host *vha, + struct qla_tgt_srr_ctio *sctio, struct qla_tgt_srr_imm *imm) +{ + struct imm_ntfy_from_isp *ntfy = + (struct imm_ntfy_from_isp *)&imm->imm_ntfy; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_cmd *cmd = sctio->cmd; + struct se_cmd *se_cmd = &cmd->se_cmd; + unsigned long flags; + int xmit_type = 0, resp = 0; + uint32_t offset; + uint16_t srr_ui; + + offset = le32_to_cpu(ntfy->u.isp24.srr_rel_offs); + srr_ui = ntfy->u.isp24.srr_ui; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf028, "SRR cmd %p, srr_ui %x\n", + cmd, srr_ui); + + switch (srr_ui) { + case SRR_IU_STATUS: + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_send_notify_ack(vha, ntfy, + 0, 0, 0, NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + xmit_type = QLA_TGT_XMIT_STATUS; + resp = 1; + break; + case SRR_IU_DATA_IN: + if (!cmd->sg || !cmd->sg_cnt) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf063, + "Unable to process SRR_IU_DATA_IN due to" + " missing cmd->sg, state: %d\n", cmd->state); + dump_stack(); + goto out_reject; + } + if (se_cmd->scsi_status != 0) { + ql_dbg(ql_dbg_tgt, vha, 0xe02a, + "Rejecting SRR_IU_DATA_IN with non GOOD " + "scsi_status\n"); + goto out_reject; + } + cmd->bufflen = se_cmd->data_length; + + if (qlt_has_data(cmd)) { + if (qlt_srr_adjust_data(cmd, offset, &xmit_type) != 0) + goto out_reject; + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_send_notify_ack(vha, ntfy, + 0, 0, 0, NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + resp = 1; + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf064, + "qla_target(%d): SRR for in data for cmd " + "without them (tag %d, SCSI status %d), " + "reject", vha->vp_idx, cmd->tag, + cmd->se_cmd.scsi_status); + goto out_reject; + } + break; + case SRR_IU_DATA_OUT: + if (!cmd->sg || !cmd->sg_cnt) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf065, + "Unable to process SRR_IU_DATA_OUT due to" + " missing cmd->sg\n"); + dump_stack(); + goto out_reject; + } + if (se_cmd->scsi_status != 0) { + ql_dbg(ql_dbg_tgt, vha, 0xe02b, + "Rejecting SRR_IU_DATA_OUT" + " with non GOOD scsi_status\n"); + goto out_reject; + } + cmd->bufflen = se_cmd->data_length; + + if (qlt_has_data(cmd)) { + if (qlt_srr_adjust_data(cmd, offset, &xmit_type) != 0) + goto out_reject; + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_send_notify_ack(vha, ntfy, + 0, 0, 0, NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + if (xmit_type & QLA_TGT_XMIT_DATA) + qlt_rdy_to_xfer(cmd); + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf066, + "qla_target(%d): SRR for out data for cmd " + "without them (tag %d, SCSI status %d), " + "reject", vha->vp_idx, cmd->tag, + cmd->se_cmd.scsi_status); + goto out_reject; + } + break; + default: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf067, + "qla_target(%d): Unknown srr_ui value %x", + vha->vp_idx, srr_ui); + goto out_reject; + } + + /* Transmit response in case of status and data-in cases */ + if (resp) + qlt_xmit_response(cmd, xmit_type, se_cmd->scsi_status); + + return; + +out_reject: + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_send_notify_ack(vha, ntfy, 0, 0, 0, + NOTIFY_ACK_SRR_FLAGS_REJECT, + NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM, + NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL); + if (cmd->state == QLA_TGT_STATE_NEED_DATA) { + cmd->state = QLA_TGT_STATE_DATA_IN; + dump_stack(); + } else + qlt_send_term_exchange(vha, cmd, &cmd->atio, 1); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +static void qlt_reject_free_srr_imm(struct scsi_qla_host *vha, + struct qla_tgt_srr_imm *imm, int ha_locked) +{ + struct qla_hw_data *ha = vha->hw; + unsigned long flags = 0; + + if (!ha_locked) + spin_lock_irqsave(&ha->hardware_lock, flags); + + qlt_send_notify_ack(vha, (void *)&imm->imm_ntfy, 0, 0, 0, + NOTIFY_ACK_SRR_FLAGS_REJECT, + NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM, + NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL); + + if (!ha_locked) + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + kfree(imm); +} + +static void qlt_handle_srr_work(struct work_struct *work) +{ + struct qla_tgt *tgt = container_of(work, struct qla_tgt, srr_work); + struct scsi_qla_host *vha = tgt->vha; + struct qla_tgt_srr_ctio *sctio; + unsigned long flags; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf029, "Entering SRR work (tgt %p)\n", + tgt); + +restart: + spin_lock_irqsave(&tgt->srr_lock, flags); + list_for_each_entry(sctio, &tgt->srr_ctio_list, srr_list_entry) { + struct qla_tgt_srr_imm *imm, *i, *ti; + struct qla_tgt_cmd *cmd; + struct se_cmd *se_cmd; + + imm = NULL; + list_for_each_entry_safe(i, ti, &tgt->srr_imm_list, + srr_list_entry) { + if (i->srr_id == sctio->srr_id) { + list_del(&i->srr_list_entry); + if (imm) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf068, + "qla_target(%d): There must be " + "only one IMM SRR per CTIO SRR " + "(IMM SRR %p, id %d, CTIO %p\n", + vha->vp_idx, i, i->srr_id, sctio); + qlt_reject_free_srr_imm(tgt->vha, i, 0); + } else + imm = i; + } + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf02a, + "IMM SRR %p, CTIO SRR %p (id %d)\n", imm, sctio, + sctio->srr_id); + + if (imm == NULL) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf02b, + "Not found matching IMM for SRR CTIO (id %d)\n", + sctio->srr_id); + continue; + } else + list_del(&sctio->srr_list_entry); + + spin_unlock_irqrestore(&tgt->srr_lock, flags); + + cmd = sctio->cmd; + /* + * Reset qla_tgt_cmd SRR values and SGL pointer+count to follow + * tcm_qla2xxx_write_pending() and tcm_qla2xxx_queue_data_in() + * logic.. + */ + cmd->offset = 0; + if (cmd->free_sg) { + kfree(cmd->sg); + cmd->sg = NULL; + cmd->free_sg = 0; + } + se_cmd = &cmd->se_cmd; + + cmd->sg_cnt = se_cmd->t_data_nents; + cmd->sg = se_cmd->t_data_sg; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf02c, + "SRR cmd %p (se_cmd %p, tag %d, op %x), " + "sg_cnt=%d, offset=%d", cmd, &cmd->se_cmd, cmd->tag, + se_cmd->t_task_cdb[0], cmd->sg_cnt, cmd->offset); + + qlt_handle_srr(vha, sctio, imm); + + kfree(imm); + kfree(sctio); + goto restart; + } + spin_unlock_irqrestore(&tgt->srr_lock, flags); +} + +/* ha->hardware_lock supposed to be held on entry */ +static void qlt_prepare_srr_imm(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *iocb) +{ + struct qla_tgt_srr_imm *imm; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + struct qla_tgt_srr_ctio *sctio; + + tgt->imm_srr_id++; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf02d, "qla_target(%d): SRR received\n", + vha->vp_idx); + + imm = kzalloc(sizeof(*imm), GFP_ATOMIC); + if (imm != NULL) { + memcpy(&imm->imm_ntfy, iocb, sizeof(imm->imm_ntfy)); + + /* IRQ is already OFF */ + spin_lock(&tgt->srr_lock); + imm->srr_id = tgt->imm_srr_id; + list_add_tail(&imm->srr_list_entry, + &tgt->srr_imm_list); + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf02e, + "IMM NTFY SRR %p added (id %d, ui %x)\n", + imm, imm->srr_id, iocb->u.isp24.srr_ui); + if (tgt->imm_srr_id == tgt->ctio_srr_id) { + int found = 0; + list_for_each_entry(sctio, &tgt->srr_ctio_list, + srr_list_entry) { + if (sctio->srr_id == imm->srr_id) { + found = 1; + break; + } + } + if (found) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf02f, "%s", + "Scheduling srr work\n"); + schedule_work(&tgt->srr_work); + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf030, + "qla_target(%d): imm_srr_id " + "== ctio_srr_id (%d), but there is no " + "corresponding SRR CTIO, deleting IMM " + "SRR %p\n", vha->vp_idx, tgt->ctio_srr_id, + imm); + list_del(&imm->srr_list_entry); + + kfree(imm); + + spin_unlock(&tgt->srr_lock); + goto out_reject; + } + } + spin_unlock(&tgt->srr_lock); + } else { + struct qla_tgt_srr_ctio *ts; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf069, + "qla_target(%d): Unable to allocate SRR IMM " + "entry, SRR request will be rejected\n", vha->vp_idx); + + /* IRQ is already OFF */ + spin_lock(&tgt->srr_lock); + list_for_each_entry_safe(sctio, ts, &tgt->srr_ctio_list, + srr_list_entry) { + if (sctio->srr_id == tgt->imm_srr_id) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf031, + "CTIO SRR %p deleted (id %d)\n", + sctio, sctio->srr_id); + list_del(&sctio->srr_list_entry); + qlt_send_term_exchange(vha, sctio->cmd, + &sctio->cmd->atio, 1); + kfree(sctio); + } + } + spin_unlock(&tgt->srr_lock); + goto out_reject; + } + + return; + +out_reject: + qlt_send_notify_ack(vha, iocb, 0, 0, 0, + NOTIFY_ACK_SRR_FLAGS_REJECT, + NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM, + NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL); +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +static void qlt_handle_imm_notify(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *iocb) +{ + struct qla_hw_data *ha = vha->hw; + uint32_t add_flags = 0; + int send_notify_ack = 1; + uint16_t status; + + status = le16_to_cpu(iocb->u.isp2x.status); + switch (status) { + case IMM_NTFY_LIP_RESET: + { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf032, + "qla_target(%d): LIP reset (loop %#x), subcode %x\n", + vha->vp_idx, le16_to_cpu(iocb->u.isp24.nport_handle), + iocb->u.isp24.status_subcode); + + if (qlt_reset(vha, iocb, QLA_TGT_ABORT_ALL) == 0) + send_notify_ack = 0; + break; + } + + case IMM_NTFY_LIP_LINK_REINIT: + { + struct qla_tgt *tgt = ha->tgt.qla_tgt; + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf033, + "qla_target(%d): LINK REINIT (loop %#x, " + "subcode %x)\n", vha->vp_idx, + le16_to_cpu(iocb->u.isp24.nport_handle), + iocb->u.isp24.status_subcode); + if (tgt->link_reinit_iocb_pending) { + qlt_send_notify_ack(vha, &tgt->link_reinit_iocb, + 0, 0, 0, 0, 0, 0); + } + memcpy(&tgt->link_reinit_iocb, iocb, sizeof(*iocb)); + tgt->link_reinit_iocb_pending = 1; + /* + * QLogic requires to wait after LINK REINIT for possible + * PDISC or ADISC ELS commands + */ + send_notify_ack = 0; + break; + } + + case IMM_NTFY_PORT_LOGOUT: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf034, + "qla_target(%d): Port logout (loop " + "%#x, subcode %x)\n", vha->vp_idx, + le16_to_cpu(iocb->u.isp24.nport_handle), + iocb->u.isp24.status_subcode); + + if (qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS_SESS) == 0) + send_notify_ack = 0; + /* The sessions will be cleared in the callback, if needed */ + break; + + case IMM_NTFY_GLBL_TPRLO: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf035, + "qla_target(%d): Global TPRLO (%x)\n", vha->vp_idx, status); + if (qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS) == 0) + send_notify_ack = 0; + /* The sessions will be cleared in the callback, if needed */ + break; + + case IMM_NTFY_PORT_CONFIG: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf036, + "qla_target(%d): Port config changed (%x)\n", vha->vp_idx, + status); + if (qlt_reset(vha, iocb, QLA_TGT_ABORT_ALL) == 0) + send_notify_ack = 0; + /* The sessions will be cleared in the callback, if needed */ + break; + + case IMM_NTFY_GLBL_LOGO: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06a, + "qla_target(%d): Link failure detected\n", + vha->vp_idx); + /* I_T nexus loss */ + if (qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS) == 0) + send_notify_ack = 0; + break; + + case IMM_NTFY_IOCB_OVERFLOW: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06b, + "qla_target(%d): Cannot provide requested " + "capability (IOCB overflowed the immediate notify " + "resource count)\n", vha->vp_idx); + break; + + case IMM_NTFY_ABORT_TASK: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf037, + "qla_target(%d): Abort Task (S %08x I %#x -> " + "L %#x)\n", vha->vp_idx, + le16_to_cpu(iocb->u.isp2x.seq_id), + GET_TARGET_ID(ha, (struct atio_from_isp *)iocb), + le16_to_cpu(iocb->u.isp2x.lun)); + if (qlt_abort_task(vha, iocb) == 0) + send_notify_ack = 0; + break; + + case IMM_NTFY_RESOURCE: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06c, + "qla_target(%d): Out of resources, host %ld\n", + vha->vp_idx, vha->host_no); + break; + + case IMM_NTFY_MSG_RX: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf038, + "qla_target(%d): Immediate notify task %x\n", + vha->vp_idx, iocb->u.isp2x.task_flags); + if (qlt_handle_task_mgmt(vha, iocb) == 0) + send_notify_ack = 0; + break; + + case IMM_NTFY_ELS: + if (qlt_24xx_handle_els(vha, iocb) == 0) + send_notify_ack = 0; + break; + + case IMM_NTFY_SRR: + qlt_prepare_srr_imm(vha, iocb); + send_notify_ack = 0; + break; + + default: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06d, + "qla_target(%d): Received unknown immediate " + "notify status %x\n", vha->vp_idx, status); + break; + } + + if (send_notify_ack) + qlt_send_notify_ack(vha, iocb, add_flags, 0, 0, 0, 0, 0); +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + * This function sends busy to ISP 2xxx or 24xx. + */ +static void qlt_send_busy(struct scsi_qla_host *vha, + struct atio_from_isp *atio, uint16_t status) +{ + struct ctio7_to_24xx *ctio24; + struct qla_hw_data *ha = vha->hw; + request_t *pkt; + struct qla_tgt_sess *sess = NULL; + + sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, + atio->u.isp24.fcp_hdr.s_id); + if (!sess) { + qlt_send_term_exchange(vha, NULL, atio, 1); + return; + } + /* Sending marker isn't necessary, since we called from ISR */ + + pkt = (request_t *)qla2x00_alloc_iocbs(vha, NULL); + if (!pkt) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06e, + "qla_target(%d): %s failed: unable to allocate " + "request packet", vha->vp_idx, __func__); + return; + } + + pkt->entry_count = 1; + pkt->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK; + + ctio24 = (struct ctio7_to_24xx *)pkt; + ctio24->entry_type = CTIO_TYPE7; + ctio24->nport_handle = sess->loop_id; + ctio24->timeout = __constant_cpu_to_le16(QLA_TGT_TIMEOUT); + ctio24->vp_index = vha->vp_idx; + ctio24->initiator_id[0] = atio->u.isp24.fcp_hdr.s_id[2]; + ctio24->initiator_id[1] = atio->u.isp24.fcp_hdr.s_id[1]; + ctio24->initiator_id[2] = atio->u.isp24.fcp_hdr.s_id[0]; + ctio24->exchange_addr = atio->u.isp24.exchange_addr; + ctio24->u.status1.flags = (atio->u.isp24.attr << 9) | + __constant_cpu_to_le16( + CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_SEND_STATUS | + CTIO7_FLAGS_DONT_RET_CTIO); + /* + * CTIO from fw w/o se_cmd doesn't provide enough info to retry it, + * if the explicit conformation is used. + */ + ctio24->u.status1.ox_id = swab16(atio->u.isp24.fcp_hdr.ox_id); + ctio24->u.status1.scsi_status = cpu_to_le16(status); + ctio24->u.status1.residual = get_unaligned((uint32_t *) + &atio->u.isp24.fcp_cmnd.add_cdb[ + atio->u.isp24.fcp_cmnd.add_cdb_len]); + if (ctio24->u.status1.residual != 0) + ctio24->u.status1.scsi_status |= SS_RESIDUAL_UNDER; + + qla2x00_start_iocbs(vha, vha->req); +} + +/* ha->hardware_lock supposed to be held on entry */ +/* called via callback from qla2xxx */ +static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, + struct atio_from_isp *atio) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + int rc; + + if (unlikely(tgt == NULL)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf039, + "ATIO pkt, but no tgt (ha %p)", ha); + return; + } + ql_dbg(ql_dbg_tgt, vha, 0xe02c, + "qla_target(%d): ATIO pkt %p: type %02x count %02x", + vha->vp_idx, atio, atio->u.raw.entry_type, + atio->u.raw.entry_count); + /* + * In tgt_stop mode we also should allow all requests to pass. + * Otherwise, some commands can stuck. + */ + + tgt->irq_cmd_count++; + + switch (atio->u.raw.entry_type) { + case ATIO_TYPE7: + ql_dbg(ql_dbg_tgt, vha, 0xe02d, + "ATIO_TYPE7 instance %d, lun %Lx, read/write %d/%d, " + "add_cdb_len %d, data_length %04x, s_id %x:%x:%x\n", + vha->vp_idx, atio->u.isp24.fcp_cmnd.lun, + atio->u.isp24.fcp_cmnd.rddata, + atio->u.isp24.fcp_cmnd.wrdata, + atio->u.isp24.fcp_cmnd.add_cdb_len, + be32_to_cpu(get_unaligned((uint32_t *) + &atio->u.isp24.fcp_cmnd.add_cdb[ + atio->u.isp24.fcp_cmnd.add_cdb_len])), + atio->u.isp24.fcp_hdr.s_id[0], + atio->u.isp24.fcp_hdr.s_id[1], + atio->u.isp24.fcp_hdr.s_id[2]); + + if (unlikely(atio->u.isp24.exchange_addr == + ATIO_EXCHANGE_ADDRESS_UNKNOWN)) { + ql_dbg(ql_dbg_tgt, vha, 0xe058, + "qla_target(%d): ATIO_TYPE7 " + "received with UNKNOWN exchange address, " + "sending QUEUE_FULL\n", vha->vp_idx); + qlt_send_busy(vha, atio, SAM_STAT_TASK_SET_FULL); + break; + } + if (likely(atio->u.isp24.fcp_cmnd.task_mgmt_flags == 0)) + rc = qlt_handle_cmd_for_atio(vha, atio); + else + rc = qlt_handle_task_mgmt(vha, atio); + if (unlikely(rc != 0)) { + if (rc == -ESRCH) { +#if 1 /* With TERM EXCHANGE some FC cards refuse to boot */ + qlt_send_busy(vha, atio, SAM_STAT_BUSY); +#else + qlt_send_term_exchange(vha, NULL, atio, 1); +#endif + } else { + if (tgt->tgt_stop) { + ql_dbg(ql_dbg_tgt, vha, 0xe059, + "qla_target: Unable to send " + "command to target for req, " + "ignoring.\n"); + } else { + ql_dbg(ql_dbg_tgt, vha, 0xe05a, + "qla_target(%d): Unable to send " + "command to target, sending BUSY " + "status.\n", vha->vp_idx); + qlt_send_busy(vha, atio, SAM_STAT_BUSY); + } + } + } + break; + + case IMMED_NOTIFY_TYPE: + { + if (unlikely(atio->u.isp2x.entry_status != 0)) { + ql_dbg(ql_dbg_tgt, vha, 0xe05b, + "qla_target(%d): Received ATIO packet %x " + "with error status %x\n", vha->vp_idx, + atio->u.raw.entry_type, + atio->u.isp2x.entry_status); + break; + } + ql_dbg(ql_dbg_tgt, vha, 0xe02e, "%s", "IMMED_NOTIFY ATIO"); + qlt_handle_imm_notify(vha, (struct imm_ntfy_from_isp *)atio); + break; + } + + default: + ql_dbg(ql_dbg_tgt, vha, 0xe05c, + "qla_target(%d): Received unknown ATIO atio " + "type %x\n", vha->vp_idx, atio->u.raw.entry_type); + break; + } + + tgt->irq_cmd_count--; +} + +/* ha->hardware_lock supposed to be held on entry */ +/* called via callback from qla2xxx */ +static void qlt_response_pkt(struct scsi_qla_host *vha, response_t *pkt) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + + if (unlikely(tgt == NULL)) { + ql_dbg(ql_dbg_tgt, vha, 0xe05d, + "qla_target(%d): Response pkt %x received, but no " + "tgt (ha %p)\n", vha->vp_idx, pkt->entry_type, ha); + return; + } + + ql_dbg(ql_dbg_tgt, vha, 0xe02f, + "qla_target(%d): response pkt %p: T %02x C %02x S %02x " + "handle %#x\n", vha->vp_idx, pkt, pkt->entry_type, + pkt->entry_count, pkt->entry_status, pkt->handle); + + /* + * In tgt_stop mode we also should allow all requests to pass. + * Otherwise, some commands can stuck. + */ + + tgt->irq_cmd_count++; + + switch (pkt->entry_type) { + case CTIO_TYPE7: + { + struct ctio7_from_24xx *entry = (struct ctio7_from_24xx *)pkt; + ql_dbg(ql_dbg_tgt, vha, 0xe030, "CTIO_TYPE7: instance %d\n", + vha->vp_idx); + qlt_do_ctio_completion(vha, entry->handle, + le16_to_cpu(entry->status)|(pkt->entry_status << 16), + entry); + break; + } + + case ACCEPT_TGT_IO_TYPE: + { + struct atio_from_isp *atio = (struct atio_from_isp *)pkt; + int rc; + ql_dbg(ql_dbg_tgt, vha, 0xe031, + "ACCEPT_TGT_IO instance %d status %04x " + "lun %04x read/write %d data_length %04x " + "target_id %02x rx_id %04x\n ", vha->vp_idx, + le16_to_cpu(atio->u.isp2x.status), + le16_to_cpu(atio->u.isp2x.lun), + atio->u.isp2x.execution_codes, + le32_to_cpu(atio->u.isp2x.data_length), GET_TARGET_ID(ha, + atio), atio->u.isp2x.rx_id); + if (atio->u.isp2x.status != + __constant_cpu_to_le16(ATIO_CDB_VALID)) { + ql_dbg(ql_dbg_tgt, vha, 0xe05e, + "qla_target(%d): ATIO with error " + "status %x received\n", vha->vp_idx, + le16_to_cpu(atio->u.isp2x.status)); + break; + } + ql_dbg(ql_dbg_tgt, vha, 0xe032, + "FCP CDB: 0x%02x, sizeof(cdb): %lu", + atio->u.isp2x.cdb[0], (unsigned long + int)sizeof(atio->u.isp2x.cdb)); + + rc = qlt_handle_cmd_for_atio(vha, atio); + if (unlikely(rc != 0)) { + if (rc == -ESRCH) { +#if 1 /* With TERM EXCHANGE some FC cards refuse to boot */ + qlt_send_busy(vha, atio, 0); +#else + qlt_send_term_exchange(vha, NULL, atio, 1); +#endif + } else { + if (tgt->tgt_stop) { + ql_dbg(ql_dbg_tgt, vha, 0xe05f, + "qla_target: Unable to send " + "command to target, sending TERM " + "EXCHANGE for rsp\n"); + qlt_send_term_exchange(vha, NULL, + atio, 1); + } else { + ql_dbg(ql_dbg_tgt, vha, 0xe060, + "qla_target(%d): Unable to send " + "command to target, sending BUSY " + "status\n", vha->vp_idx); + qlt_send_busy(vha, atio, 0); + } + } + } + } + break; + + case CONTINUE_TGT_IO_TYPE: + { + struct ctio_to_2xxx *entry = (struct ctio_to_2xxx *)pkt; + ql_dbg(ql_dbg_tgt, vha, 0xe033, + "CONTINUE_TGT_IO: instance %d\n", vha->vp_idx); + qlt_do_ctio_completion(vha, entry->handle, + le16_to_cpu(entry->status)|(pkt->entry_status << 16), + entry); + break; + } + + case CTIO_A64_TYPE: + { + struct ctio_to_2xxx *entry = (struct ctio_to_2xxx *)pkt; + ql_dbg(ql_dbg_tgt, vha, 0xe034, "CTIO_A64: instance %d\n", + vha->vp_idx); + qlt_do_ctio_completion(vha, entry->handle, + le16_to_cpu(entry->status)|(pkt->entry_status << 16), + entry); + break; + } + + case IMMED_NOTIFY_TYPE: + ql_dbg(ql_dbg_tgt, vha, 0xe035, "%s", "IMMED_NOTIFY\n"); + qlt_handle_imm_notify(vha, (struct imm_ntfy_from_isp *)pkt); + break; + + case NOTIFY_ACK_TYPE: + if (tgt->notify_ack_expected > 0) { + struct nack_to_isp *entry = (struct nack_to_isp *)pkt; + ql_dbg(ql_dbg_tgt, vha, 0xe036, + "NOTIFY_ACK seq %08x status %x\n", + le16_to_cpu(entry->u.isp2x.seq_id), + le16_to_cpu(entry->u.isp2x.status)); + tgt->notify_ack_expected--; + if (entry->u.isp2x.status != + __constant_cpu_to_le16(NOTIFY_ACK_SUCCESS)) { + ql_dbg(ql_dbg_tgt, vha, 0xe061, + "qla_target(%d): NOTIFY_ACK " + "failed %x\n", vha->vp_idx, + le16_to_cpu(entry->u.isp2x.status)); + } + } else { + ql_dbg(ql_dbg_tgt, vha, 0xe062, + "qla_target(%d): Unexpected NOTIFY_ACK received\n", + vha->vp_idx); + } + break; + + case ABTS_RECV_24XX: + ql_dbg(ql_dbg_tgt, vha, 0xe037, + "ABTS_RECV_24XX: instance %d\n", vha->vp_idx); + qlt_24xx_handle_abts(vha, (struct abts_recv_from_24xx *)pkt); + break; + + case ABTS_RESP_24XX: + if (tgt->abts_resp_expected > 0) { + struct abts_resp_from_24xx_fw *entry = + (struct abts_resp_from_24xx_fw *)pkt; + ql_dbg(ql_dbg_tgt, vha, 0xe038, + "ABTS_RESP_24XX: compl_status %x\n", + entry->compl_status); + tgt->abts_resp_expected--; + if (le16_to_cpu(entry->compl_status) != + ABTS_RESP_COMPL_SUCCESS) { + if ((entry->error_subcode1 == 0x1E) && + (entry->error_subcode2 == 0)) { + /* + * We've got a race here: aborted + * exchange not terminated, i.e. + * response for the aborted command was + * sent between the abort request was + * received and processed. + * Unfortunately, the firmware has a + * silly requirement that all aborted + * exchanges must be explicitely + * terminated, otherwise it refuses to + * send responses for the abort + * requests. So, we have to + * (re)terminate the exchange and retry + * the abort response. + */ + qlt_24xx_retry_term_exchange(vha, + entry); + } else + ql_dbg(ql_dbg_tgt, vha, 0xe063, + "qla_target(%d): ABTS_RESP_24XX " + "failed %x (subcode %x:%x)", + vha->vp_idx, entry->compl_status, + entry->error_subcode1, + entry->error_subcode2); + } + } else { + ql_dbg(ql_dbg_tgt, vha, 0xe064, + "qla_target(%d): Unexpected ABTS_RESP_24XX " + "received\n", vha->vp_idx); + } + break; + + default: + ql_dbg(ql_dbg_tgt, vha, 0xe065, + "qla_target(%d): Received unknown response pkt " + "type %x\n", vha->vp_idx, pkt->entry_type); + break; + } + + tgt->irq_cmd_count--; +} + +/* + * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire + */ +void qlt_async_event(uint16_t code, struct scsi_qla_host *vha, + uint16_t *mailbox) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + int reason_code; + + ql_dbg(ql_dbg_tgt, vha, 0xe039, + "scsi(%ld): ha state %d init_done %d oper_mode %d topo %d\n", + vha->host_no, atomic_read(&vha->loop_state), vha->flags.init_done, + ha->operating_mode, ha->current_topology); + + if (!ha->tgt.tgt_ops) + return; + + if (unlikely(tgt == NULL)) { + ql_dbg(ql_dbg_tgt, vha, 0xe03a, + "ASYNC EVENT %#x, but no tgt (ha %p)\n", code, ha); + return; + } + + if (((code == MBA_POINT_TO_POINT) || (code == MBA_CHG_IN_CONNECTION)) && + IS_QLA2100(ha)) + return; + /* + * In tgt_stop mode we also should allow all requests to pass. + * Otherwise, some commands can stuck. + */ + + tgt->irq_cmd_count++; + + switch (code) { + case MBA_RESET: /* Reset */ + case MBA_SYSTEM_ERR: /* System Error */ + case MBA_REQ_TRANSFER_ERR: /* Request Transfer Error */ + case MBA_RSP_TRANSFER_ERR: /* Response Transfer Error */ + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03a, + "qla_target(%d): System error async event %#x " + "occured", vha->vp_idx, code); + break; + case MBA_WAKEUP_THRES: /* Request Queue Wake-up. */ + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + break; + + case MBA_LOOP_UP: + { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03b, + "qla_target(%d): Async LOOP_UP occured " + "(m[1]=%x, m[2]=%x, m[3]=%x, m[4]=%x)", vha->vp_idx, + le16_to_cpu(mailbox[1]), le16_to_cpu(mailbox[2]), + le16_to_cpu(mailbox[3]), le16_to_cpu(mailbox[4])); + if (tgt->link_reinit_iocb_pending) { + qlt_send_notify_ack(vha, (void *)&tgt->link_reinit_iocb, + 0, 0, 0, 0, 0, 0); + tgt->link_reinit_iocb_pending = 0; + } + break; + } + + case MBA_LIP_OCCURRED: + case MBA_LOOP_DOWN: + case MBA_LIP_RESET: + case MBA_RSCN_UPDATE: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03c, + "qla_target(%d): Async event %#x occured " + "(m[1]=%x, m[2]=%x, m[3]=%x, m[4]=%x)", vha->vp_idx, code, + le16_to_cpu(mailbox[1]), le16_to_cpu(mailbox[2]), + le16_to_cpu(mailbox[3]), le16_to_cpu(mailbox[4])); + break; + + case MBA_PORT_UPDATE: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03d, + "qla_target(%d): Port update async event %#x " + "occured: updating the ports database (m[1]=%x, m[2]=%x, " + "m[3]=%x, m[4]=%x)", vha->vp_idx, code, + le16_to_cpu(mailbox[1]), le16_to_cpu(mailbox[2]), + le16_to_cpu(mailbox[3]), le16_to_cpu(mailbox[4])); + reason_code = le16_to_cpu(mailbox[2]); + if (reason_code == 0x4) + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03e, + "Async MB 2: Got PLOGI Complete\n"); + else if (reason_code == 0x7) + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03f, + "Async MB 2: Port Logged Out\n"); + break; + + default: + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf040, + "qla_target(%d): Async event %#x occured: " + "ignore (m[1]=%x, m[2]=%x, m[3]=%x, m[4]=%x)", vha->vp_idx, + code, le16_to_cpu(mailbox[1]), le16_to_cpu(mailbox[2]), + le16_to_cpu(mailbox[3]), le16_to_cpu(mailbox[4])); + break; + } + + tgt->irq_cmd_count--; +} + +static fc_port_t *qlt_get_port_database(struct scsi_qla_host *vha, + uint16_t loop_id) +{ + fc_port_t *fcport; + int rc; + + fcport = kzalloc(sizeof(*fcport), GFP_KERNEL); + if (!fcport) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06f, + "qla_target(%d): Allocation of tmp FC port failed", + vha->vp_idx); + return NULL; + } + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf041, "loop_id %d", loop_id); + + fcport->loop_id = loop_id; + + rc = qla2x00_get_port_database(vha, fcport, 0); + if (rc != QLA_SUCCESS) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf070, + "qla_target(%d): Failed to retrieve fcport " + "information -- get_port_database() returned %x " + "(loop_id=0x%04x)", vha->vp_idx, rc, loop_id); + kfree(fcport); + return NULL; + } + + return fcport; +} + +/* Must be called under tgt_mutex */ +static struct qla_tgt_sess *qlt_make_local_sess(struct scsi_qla_host *vha, + uint8_t *s_id) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess = NULL; + fc_port_t *fcport = NULL; + int rc, global_resets; + uint16_t loop_id = 0; + +retry: + global_resets = atomic_read(&ha->tgt.qla_tgt->tgt_global_resets_count); + + rc = qla24xx_get_loop_id(vha, s_id, &loop_id); + if (rc != 0) { + if ((s_id[0] == 0xFF) && + (s_id[1] == 0xFC)) { + /* + * This is Domain Controller, so it should be + * OK to drop SCSI commands from it. + */ + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf042, + "Unable to find initiator with S_ID %x:%x:%x", + s_id[0], s_id[1], s_id[2]); + } else + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf071, + "qla_target(%d): Unable to find " + "initiator with S_ID %x:%x:%x", + vha->vp_idx, s_id[0], s_id[1], + s_id[2]); + return NULL; + } + + fcport = qlt_get_port_database(vha, loop_id); + if (!fcport) + return NULL; + + if (global_resets != + atomic_read(&ha->tgt.qla_tgt->tgt_global_resets_count)) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf043, + "qla_target(%d): global reset during session discovery " + "(counter was %d, new %d), retrying", vha->vp_idx, + global_resets, + atomic_read(&ha->tgt.qla_tgt->tgt_global_resets_count)); + goto retry; + } + + sess = qlt_create_sess(vha, fcport, true); + + kfree(fcport); + return sess; +} + +static void qlt_abort_work(struct qla_tgt *tgt, + struct qla_tgt_sess_work_param *prm) +{ + struct scsi_qla_host *vha = tgt->vha; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess = NULL; + unsigned long flags; + uint32_t be_s_id; + uint8_t s_id[3]; + int rc; + + spin_lock_irqsave(&ha->hardware_lock, flags); + + if (tgt->tgt_stop) + goto out_term; + + s_id[0] = prm->abts.fcp_hdr_le.s_id[2]; + s_id[1] = prm->abts.fcp_hdr_le.s_id[1]; + s_id[2] = prm->abts.fcp_hdr_le.s_id[0]; + + sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, + (unsigned char *)&be_s_id); + if (!sess) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + mutex_lock(&ha->tgt.tgt_mutex); + sess = qlt_make_local_sess(vha, s_id); + /* sess has got an extra creation ref */ + mutex_unlock(&ha->tgt.tgt_mutex); + + spin_lock_irqsave(&ha->hardware_lock, flags); + if (!sess) + goto out_term; + } else { + kref_get(&sess->se_sess->sess_kref); + } + + if (tgt->tgt_stop) + goto out_term; + + rc = __qlt_24xx_handle_abts(vha, &prm->abts, sess); + if (rc != 0) + goto out_term; + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + ha->tgt.tgt_ops->put_sess(sess); + return; + +out_term: + qlt_24xx_send_abts_resp(vha, &prm->abts, FCP_TMF_REJECTED, false); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + if (sess) + ha->tgt.tgt_ops->put_sess(sess); +} + +static void qlt_tmr_work(struct qla_tgt *tgt, + struct qla_tgt_sess_work_param *prm) +{ + struct atio_from_isp *a = &prm->tm_iocb2; + struct scsi_qla_host *vha = tgt->vha; + struct qla_hw_data *ha = vha->hw; + struct qla_tgt_sess *sess = NULL; + unsigned long flags; + uint8_t *s_id = NULL; /* to hide compiler warnings */ + int rc; + uint32_t lun, unpacked_lun; + int lun_size, fn; + void *iocb; + + spin_lock_irqsave(&ha->hardware_lock, flags); + + if (tgt->tgt_stop) + goto out_term; + + s_id = prm->tm_iocb2.u.isp24.fcp_hdr.s_id; + sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, s_id); + if (!sess) { + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + mutex_lock(&ha->tgt.tgt_mutex); + sess = qlt_make_local_sess(vha, s_id); + /* sess has got an extra creation ref */ + mutex_unlock(&ha->tgt.tgt_mutex); + + spin_lock_irqsave(&ha->hardware_lock, flags); + if (!sess) + goto out_term; + } else { + kref_get(&sess->se_sess->sess_kref); + } + + iocb = a; + lun = a->u.isp24.fcp_cmnd.lun; + lun_size = sizeof(lun); + fn = a->u.isp24.fcp_cmnd.task_mgmt_flags; + unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun); + + rc = qlt_issue_task_mgmt(sess, unpacked_lun, fn, iocb, 0); + if (rc != 0) + goto out_term; + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + ha->tgt.tgt_ops->put_sess(sess); + return; + +out_term: + qlt_send_term_exchange(vha, NULL, &prm->tm_iocb2, 1); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + if (sess) + ha->tgt.tgt_ops->put_sess(sess); +} + +static void qlt_sess_work_fn(struct work_struct *work) +{ + struct qla_tgt *tgt = container_of(work, struct qla_tgt, sess_work); + struct scsi_qla_host *vha = tgt->vha; + unsigned long flags; + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf000, "Sess work (tgt %p)", tgt); + + spin_lock_irqsave(&tgt->sess_work_lock, flags); + while (!list_empty(&tgt->sess_works_list)) { + struct qla_tgt_sess_work_param *prm = list_entry( + tgt->sess_works_list.next, typeof(*prm), + sess_works_list_entry); + + /* + * This work can be scheduled on several CPUs at time, so we + * must delete the entry to eliminate double processing + */ + list_del(&prm->sess_works_list_entry); + + spin_unlock_irqrestore(&tgt->sess_work_lock, flags); + + switch (prm->type) { + case QLA_TGT_SESS_WORK_ABORT: + qlt_abort_work(tgt, prm); + break; + case QLA_TGT_SESS_WORK_TM: + qlt_tmr_work(tgt, prm); + break; + default: + BUG_ON(1); + break; + } + + spin_lock_irqsave(&tgt->sess_work_lock, flags); + + kfree(prm); + } + spin_unlock_irqrestore(&tgt->sess_work_lock, flags); +} + +/* Must be called under tgt_host_action_mutex */ +int qlt_add_target(struct qla_hw_data *ha, struct scsi_qla_host *base_vha) +{ + struct qla_tgt *tgt; + + if (!QLA_TGT_MODE_ENABLED()) + return 0; + + ql_dbg(ql_dbg_tgt, base_vha, 0xe03b, + "Registering target for host %ld(%p)", base_vha->host_no, ha); + + BUG_ON((ha->tgt.qla_tgt != NULL) || (ha->tgt.tgt_ops != NULL)); + + tgt = kzalloc(sizeof(struct qla_tgt), GFP_KERNEL); + if (!tgt) { + ql_dbg(ql_dbg_tgt, base_vha, 0xe066, + "Unable to allocate struct qla_tgt\n"); + return -ENOMEM; + } + + if (!(base_vha->host->hostt->supported_mode & MODE_TARGET)) + base_vha->host->hostt->supported_mode |= MODE_TARGET; + + tgt->ha = ha; + tgt->vha = base_vha; + init_waitqueue_head(&tgt->waitQ); + INIT_LIST_HEAD(&tgt->sess_list); + INIT_LIST_HEAD(&tgt->del_sess_list); + INIT_DELAYED_WORK(&tgt->sess_del_work, + (void (*)(struct work_struct *))qlt_del_sess_work_fn); + spin_lock_init(&tgt->sess_work_lock); + INIT_WORK(&tgt->sess_work, qlt_sess_work_fn); + INIT_LIST_HEAD(&tgt->sess_works_list); + spin_lock_init(&tgt->srr_lock); + INIT_LIST_HEAD(&tgt->srr_ctio_list); + INIT_LIST_HEAD(&tgt->srr_imm_list); + INIT_WORK(&tgt->srr_work, qlt_handle_srr_work); + atomic_set(&tgt->tgt_global_resets_count, 0); + + ha->tgt.qla_tgt = tgt; + + ql_dbg(ql_dbg_tgt, base_vha, 0xe067, + "qla_target(%d): using 64 Bit PCI addressing", + base_vha->vp_idx); + tgt->tgt_enable_64bit_addr = 1; + /* 3 is reserved */ + tgt->sg_tablesize = QLA_TGT_MAX_SG_24XX(base_vha->req->length - 3); + tgt->datasegs_per_cmd = QLA_TGT_DATASEGS_PER_CMD_24XX; + tgt->datasegs_per_cont = QLA_TGT_DATASEGS_PER_CONT_24XX; + + mutex_lock(&qla_tgt_mutex); + list_add_tail(&tgt->tgt_list_entry, &qla_tgt_glist); + mutex_unlock(&qla_tgt_mutex); + + return 0; +} + +/* Must be called under tgt_host_action_mutex */ +int qlt_remove_target(struct qla_hw_data *ha, struct scsi_qla_host *vha) +{ + if (!ha->tgt.qla_tgt) + return 0; + + mutex_lock(&qla_tgt_mutex); + list_del(&ha->tgt.qla_tgt->tgt_list_entry); + mutex_unlock(&qla_tgt_mutex); + + ql_dbg(ql_dbg_tgt, vha, 0xe03c, "Unregistering target for host %ld(%p)", + vha->host_no, ha); + qlt_release(ha->tgt.qla_tgt); + + return 0; +} + +static void qlt_lport_dump(struct scsi_qla_host *vha, u64 wwpn, + unsigned char *b) +{ + int i; + + pr_debug("qla2xxx HW vha->node_name: "); + for (i = 0; i < WWN_SIZE; i++) + pr_debug("%02x ", vha->node_name[i]); + pr_debug("\n"); + pr_debug("qla2xxx HW vha->port_name: "); + for (i = 0; i < WWN_SIZE; i++) + pr_debug("%02x ", vha->port_name[i]); + pr_debug("\n"); + + pr_debug("qla2xxx passed configfs WWPN: "); + put_unaligned_be64(wwpn, b); + for (i = 0; i < WWN_SIZE; i++) + pr_debug("%02x ", b[i]); + pr_debug("\n"); +} + +/** + * qla_tgt_lport_register - register lport with external module + * + * @qla_tgt_ops: Pointer for tcm_qla2xxx qla_tgt_ops + * @wwpn: Passwd FC target WWPN + * @callback: lport initialization callback for tcm_qla2xxx code + * @target_lport_ptr: pointer for tcm_qla2xxx specific lport data + */ +int qlt_lport_register(struct qla_tgt_func_tmpl *qla_tgt_ops, u64 wwpn, + int (*callback)(struct scsi_qla_host *), void *target_lport_ptr) +{ + struct qla_tgt *tgt; + struct scsi_qla_host *vha; + struct qla_hw_data *ha; + struct Scsi_Host *host; + unsigned long flags; + int rc; + u8 b[WWN_SIZE]; + + mutex_lock(&qla_tgt_mutex); + list_for_each_entry(tgt, &qla_tgt_glist, tgt_list_entry) { + vha = tgt->vha; + ha = vha->hw; + + host = vha->host; + if (!host) + continue; + + if (ha->tgt.tgt_ops != NULL) + continue; + + if (!(host->hostt->supported_mode & MODE_TARGET)) + continue; + + spin_lock_irqsave(&ha->hardware_lock, flags); + if (host->active_mode & MODE_TARGET) { + pr_debug("MODE_TARGET already active on qla2xxx(%d)\n", + host->host_no); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + continue; + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + if (!scsi_host_get(host)) { + ql_dbg(ql_dbg_tgt, vha, 0xe068, + "Unable to scsi_host_get() for" + " qla2xxx scsi_host\n"); + continue; + } + qlt_lport_dump(vha, wwpn, b); + + if (memcmp(vha->port_name, b, WWN_SIZE)) { + scsi_host_put(host); + continue; + } + /* + * Setup passed parameters ahead of invoking callback + */ + ha->tgt.tgt_ops = qla_tgt_ops; + ha->tgt.target_lport_ptr = target_lport_ptr; + rc = (*callback)(vha); + if (rc != 0) { + ha->tgt.tgt_ops = NULL; + ha->tgt.target_lport_ptr = NULL; + } + mutex_unlock(&qla_tgt_mutex); + return rc; + } + mutex_unlock(&qla_tgt_mutex); + + return -ENODEV; +} +EXPORT_SYMBOL(qlt_lport_register); + +/** + * qla_tgt_lport_deregister - Degister lport + * + * @vha: Registered scsi_qla_host pointer + */ +void qlt_lport_deregister(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct Scsi_Host *sh = vha->host; + /* + * Clear the target_lport_ptr qla_target_template pointer in qla_hw_data + */ + ha->tgt.target_lport_ptr = NULL; + ha->tgt.tgt_ops = NULL; + /* + * Release the Scsi_Host reference for the underlying qla2xxx host + */ + scsi_host_put(sh); +} +EXPORT_SYMBOL(qlt_lport_deregister); + +/* Must be called under HW lock */ +void qlt_set_mode(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + + switch (ql2x_ini_mode) { + case QLA2XXX_INI_MODE_DISABLED: + case QLA2XXX_INI_MODE_EXCLUSIVE: + vha->host->active_mode = MODE_TARGET; + break; + case QLA2XXX_INI_MODE_ENABLED: + vha->host->active_mode |= MODE_TARGET; + break; + default: + break; + } + + if (ha->tgt.ini_mode_force_reverse) + qla_reverse_ini_mode(vha); +} + +/* Must be called under HW lock */ +void qlt_clear_mode(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + + switch (ql2x_ini_mode) { + case QLA2XXX_INI_MODE_DISABLED: + vha->host->active_mode = MODE_UNKNOWN; + break; + case QLA2XXX_INI_MODE_EXCLUSIVE: + vha->host->active_mode = MODE_INITIATOR; + break; + case QLA2XXX_INI_MODE_ENABLED: + vha->host->active_mode &= ~MODE_TARGET; + break; + default: + break; + } + + if (ha->tgt.ini_mode_force_reverse) + qla_reverse_ini_mode(vha); +} + +/* + * qla_tgt_enable_vha - NO LOCK HELD + * + * host_reset, bring up w/ Target Mode Enabled + */ +void +qlt_enable_vha(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + unsigned long flags; + + if (!tgt) { + ql_dbg(ql_dbg_tgt, vha, 0xe069, + "Unable to locate qla_tgt pointer from" + " struct qla_hw_data\n"); + dump_stack(); + return; + } + + spin_lock_irqsave(&ha->hardware_lock, flags); + tgt->tgt_stopped = 0; + qlt_set_mode(vha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + qla2x00_wait_for_hba_online(vha); +} +EXPORT_SYMBOL(qlt_enable_vha); + +/* + * qla_tgt_disable_vha - NO LOCK HELD + * + * Disable Target Mode and reset the adapter + */ +void +qlt_disable_vha(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct qla_tgt *tgt = ha->tgt.qla_tgt; + unsigned long flags; + + if (!tgt) { + ql_dbg(ql_dbg_tgt, vha, 0xe06a, + "Unable to locate qla_tgt pointer from" + " struct qla_hw_data\n"); + dump_stack(); + return; + } + + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_clear_mode(vha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + qla2x00_wait_for_hba_online(vha); +} + +/* + * Called from qla_init.c:qla24xx_vport_create() contex to setup + * the target mode specific struct scsi_qla_host and struct qla_hw_data + * members. + */ +void +qlt_vport_create(struct scsi_qla_host *vha, struct qla_hw_data *ha) +{ + if (!qla_tgt_mode_enabled(vha)) + return; + + mutex_init(&ha->tgt.tgt_mutex); + mutex_init(&ha->tgt.tgt_host_action_mutex); + + qlt_clear_mode(vha); + + /* + * NOTE: Currently the value is kept the same for <24xx and + * >=24xx ISPs. If it is necessary to change it, + * the check should be added for specific ISPs, + * assigning the value appropriately. + */ + ha->tgt.atio_q_length = ATIO_ENTRY_CNT_24XX; +} + +void +qlt_rff_id(struct scsi_qla_host *vha, struct ct_sns_req *ct_req) +{ + /* + * FC-4 Feature bit 0 indicates target functionality to the name server. + */ + if (qla_tgt_mode_enabled(vha)) { + if (qla_ini_mode_enabled(vha)) + ct_req->req.rff_id.fc4_feature = BIT_0 | BIT_1; + else + ct_req->req.rff_id.fc4_feature = BIT_0; + } else if (qla_ini_mode_enabled(vha)) { + ct_req->req.rff_id.fc4_feature = BIT_1; + } +} + +/* + * qlt_init_atio_q_entries() - Initializes ATIO queue entries. + * @ha: HA context + * + * Beginning of ATIO ring has initialization control block already built + * by nvram config routine. + * + * Returns 0 on success. + */ +void +qlt_init_atio_q_entries(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + uint16_t cnt; + struct atio_from_isp *pkt = (struct atio_from_isp *)ha->tgt.atio_ring; + + if (!qla_tgt_mode_enabled(vha)) + return; + + for (cnt = 0; cnt < ha->tgt.atio_q_length; cnt++) { + pkt->u.raw.signature = ATIO_PROCESSED; + pkt++; + } + +} + +/* + * qlt_24xx_process_atio_queue() - Process ATIO queue entries. + * @ha: SCSI driver HA context + */ +void +qlt_24xx_process_atio_queue(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct device_reg_24xx __iomem *reg = &ha->iobase->isp24; + struct atio_from_isp *pkt; + int cnt, i; + + if (!vha->flags.online) + return; + + while (ha->tgt.atio_ring_ptr->signature != ATIO_PROCESSED) { + pkt = (struct atio_from_isp *)ha->tgt.atio_ring_ptr; + cnt = pkt->u.raw.entry_count; + + qlt_24xx_atio_pkt_all_vps(vha, (struct atio_from_isp *)pkt); + + for (i = 0; i < cnt; i++) { + ha->tgt.atio_ring_index++; + if (ha->tgt.atio_ring_index == ha->tgt.atio_q_length) { + ha->tgt.atio_ring_index = 0; + ha->tgt.atio_ring_ptr = ha->tgt.atio_ring; + } else + ha->tgt.atio_ring_ptr++; + + pkt->u.raw.signature = ATIO_PROCESSED; + pkt = (struct atio_from_isp *)ha->tgt.atio_ring_ptr; + } + wmb(); + } + + /* Adjust ring index */ + WRT_REG_DWORD(®->atio_q_out, ha->tgt.atio_ring_index); +} + +void +qlt_24xx_config_rings(struct scsi_qla_host *vha, device_reg_t __iomem *reg) +{ + struct qla_hw_data *ha = vha->hw; + +/* FIXME: atio_q in/out for ha->mqenable=1..? */ + if (ha->mqenable) { +#if 0 + WRT_REG_DWORD(®->isp25mq.atio_q_in, 0); + WRT_REG_DWORD(®->isp25mq.atio_q_out, 0); + RD_REG_DWORD(®->isp25mq.atio_q_out); +#endif + } else { + /* Setup APTIO registers for target mode */ + WRT_REG_DWORD(®->isp24.atio_q_in, 0); + WRT_REG_DWORD(®->isp24.atio_q_out, 0); + RD_REG_DWORD(®->isp24.atio_q_out); + } +} + +void +qlt_24xx_config_nvram_stage1(struct scsi_qla_host *vha, struct nvram_24xx *nv) +{ + struct qla_hw_data *ha = vha->hw; + + if (qla_tgt_mode_enabled(vha)) { + if (!ha->tgt.saved_set) { + /* We save only once */ + ha->tgt.saved_exchange_count = nv->exchange_count; + ha->tgt.saved_firmware_options_1 = + nv->firmware_options_1; + ha->tgt.saved_firmware_options_2 = + nv->firmware_options_2; + ha->tgt.saved_firmware_options_3 = + nv->firmware_options_3; + ha->tgt.saved_set = 1; + } + + nv->exchange_count = __constant_cpu_to_le16(0xFFFF); + + /* Enable target mode */ + nv->firmware_options_1 |= __constant_cpu_to_le32(BIT_4); + + /* Disable ini mode, if requested */ + if (!qla_ini_mode_enabled(vha)) + nv->firmware_options_1 |= __constant_cpu_to_le32(BIT_5); + + /* Disable Full Login after LIP */ + nv->firmware_options_1 &= __constant_cpu_to_le32(~BIT_13); + /* Enable initial LIP */ + nv->firmware_options_1 &= __constant_cpu_to_le32(~BIT_9); + /* Enable FC tapes support */ + nv->firmware_options_2 |= __constant_cpu_to_le32(BIT_12); + /* Disable Full Login after LIP */ + nv->host_p &= __constant_cpu_to_le32(~BIT_10); + /* Enable target PRLI control */ + nv->firmware_options_2 |= __constant_cpu_to_le32(BIT_14); + } else { + if (ha->tgt.saved_set) { + nv->exchange_count = ha->tgt.saved_exchange_count; + nv->firmware_options_1 = + ha->tgt.saved_firmware_options_1; + nv->firmware_options_2 = + ha->tgt.saved_firmware_options_2; + nv->firmware_options_3 = + ha->tgt.saved_firmware_options_3; + } + return; + } + + /* out-of-order frames reassembly */ + nv->firmware_options_3 |= BIT_6|BIT_9; + + if (ha->tgt.enable_class_2) { + if (vha->flags.init_done) + fc_host_supported_classes(vha->host) = + FC_COS_CLASS2 | FC_COS_CLASS3; + + nv->firmware_options_2 |= __constant_cpu_to_le32(BIT_8); + } else { + if (vha->flags.init_done) + fc_host_supported_classes(vha->host) = FC_COS_CLASS3; + + nv->firmware_options_2 &= ~__constant_cpu_to_le32(BIT_8); + } +} + +void +qlt_24xx_config_nvram_stage2(struct scsi_qla_host *vha, + struct init_cb_24xx *icb) +{ + struct qla_hw_data *ha = vha->hw; + + if (ha->tgt.node_name_set) { + memcpy(icb->node_name, ha->tgt.tgt_node_name, WWN_SIZE); + icb->firmware_options_1 |= __constant_cpu_to_le32(BIT_14); + } +} + +int +qlt_24xx_process_response_error(struct scsi_qla_host *vha, + struct sts_entry_24xx *pkt) +{ + switch (pkt->entry_type) { + case ABTS_RECV_24XX: + case ABTS_RESP_24XX: + case CTIO_TYPE7: + case NOTIFY_ACK_TYPE: + return 1; + default: + return 0; + } +} + +void +qlt_modify_vp_config(struct scsi_qla_host *vha, + struct vp_config_entry_24xx *vpmod) +{ + if (qla_tgt_mode_enabled(vha)) + vpmod->options_idx1 &= ~BIT_5; + /* Disable ini mode, if requested */ + if (!qla_ini_mode_enabled(vha)) + vpmod->options_idx1 &= ~BIT_4; +} + +void +qlt_probe_one_stage1(struct scsi_qla_host *base_vha, struct qla_hw_data *ha) +{ + if (!QLA_TGT_MODE_ENABLED()) + return; + + mutex_init(&ha->tgt.tgt_mutex); + mutex_init(&ha->tgt.tgt_host_action_mutex); + qlt_clear_mode(base_vha); +} + +int +qlt_mem_alloc(struct qla_hw_data *ha) +{ + if (!QLA_TGT_MODE_ENABLED()) + return 0; + + ha->tgt.tgt_vp_map = kzalloc(sizeof(struct qla_tgt_vp_map) * + MAX_MULTI_ID_FABRIC, GFP_KERNEL); + if (!ha->tgt.tgt_vp_map) + return -ENOMEM; + + ha->tgt.atio_ring = dma_alloc_coherent(&ha->pdev->dev, + (ha->tgt.atio_q_length + 1) * sizeof(struct atio_from_isp), + &ha->tgt.atio_dma, GFP_KERNEL); + if (!ha->tgt.atio_ring) { + kfree(ha->tgt.tgt_vp_map); + return -ENOMEM; + } + return 0; +} + +void +qlt_mem_free(struct qla_hw_data *ha) +{ + if (!QLA_TGT_MODE_ENABLED()) + return; + + if (ha->tgt.atio_ring) { + dma_free_coherent(&ha->pdev->dev, (ha->tgt.atio_q_length + 1) * + sizeof(struct atio_from_isp), ha->tgt.atio_ring, + ha->tgt.atio_dma); + } + kfree(ha->tgt.tgt_vp_map); +} + +/* vport_slock to be held by the caller */ +void +qlt_update_vp_map(struct scsi_qla_host *vha, int cmd) +{ + if (!QLA_TGT_MODE_ENABLED()) + return; + + switch (cmd) { + case SET_VP_IDX: + vha->hw->tgt.tgt_vp_map[vha->vp_idx].vha = vha; + break; + case SET_AL_PA: + vha->hw->tgt.tgt_vp_map[vha->d_id.b.al_pa].idx = vha->vp_idx; + break; + case RESET_VP_IDX: + vha->hw->tgt.tgt_vp_map[vha->vp_idx].vha = NULL; + break; + case RESET_AL_PA: + vha->hw->tgt.tgt_vp_map[vha->d_id.b.al_pa].idx = 0; + break; + } +} + +static int __init qlt_parse_ini_mode(void) +{ + if (strcasecmp(qlini_mode, QLA2XXX_INI_MODE_STR_EXCLUSIVE) == 0) + ql2x_ini_mode = QLA2XXX_INI_MODE_EXCLUSIVE; + else if (strcasecmp(qlini_mode, QLA2XXX_INI_MODE_STR_DISABLED) == 0) + ql2x_ini_mode = QLA2XXX_INI_MODE_DISABLED; + else if (strcasecmp(qlini_mode, QLA2XXX_INI_MODE_STR_ENABLED) == 0) + ql2x_ini_mode = QLA2XXX_INI_MODE_ENABLED; + else + return false; + + return true; +} + +int __init qlt_init(void) +{ + int ret; + + if (!qlt_parse_ini_mode()) { + ql_log(ql_log_fatal, NULL, 0xe06b, + "qlt_parse_ini_mode() failed\n"); + return -EINVAL; + } + + if (!QLA_TGT_MODE_ENABLED()) + return 0; + + qla_tgt_cmd_cachep = kmem_cache_create("qla_tgt_cmd_cachep", + sizeof(struct qla_tgt_cmd), __alignof__(struct qla_tgt_cmd), 0, + NULL); + if (!qla_tgt_cmd_cachep) { + ql_log(ql_log_fatal, NULL, 0xe06c, + "kmem_cache_create for qla_tgt_cmd_cachep failed\n"); + return -ENOMEM; + } + + qla_tgt_mgmt_cmd_cachep = kmem_cache_create("qla_tgt_mgmt_cmd_cachep", + sizeof(struct qla_tgt_mgmt_cmd), __alignof__(struct + qla_tgt_mgmt_cmd), 0, NULL); + if (!qla_tgt_mgmt_cmd_cachep) { + ql_log(ql_log_fatal, NULL, 0xe06d, + "kmem_cache_create for qla_tgt_mgmt_cmd_cachep failed\n"); + ret = -ENOMEM; + goto out; + } + + qla_tgt_mgmt_cmd_mempool = mempool_create(25, mempool_alloc_slab, + mempool_free_slab, qla_tgt_mgmt_cmd_cachep); + if (!qla_tgt_mgmt_cmd_mempool) { + ql_log(ql_log_fatal, NULL, 0xe06e, + "mempool_create for qla_tgt_mgmt_cmd_mempool failed\n"); + ret = -ENOMEM; + goto out_mgmt_cmd_cachep; + } + + qla_tgt_wq = alloc_workqueue("qla_tgt_wq", 0, 0); + if (!qla_tgt_wq) { + ql_log(ql_log_fatal, NULL, 0xe06f, + "alloc_workqueue for qla_tgt_wq failed\n"); + ret = -ENOMEM; + goto out_cmd_mempool; + } + /* + * Return 1 to signal that initiator-mode is being disabled + */ + return (ql2x_ini_mode == QLA2XXX_INI_MODE_DISABLED) ? 1 : 0; + +out_cmd_mempool: + mempool_destroy(qla_tgt_mgmt_cmd_mempool); +out_mgmt_cmd_cachep: + kmem_cache_destroy(qla_tgt_mgmt_cmd_cachep); +out: + kmem_cache_destroy(qla_tgt_cmd_cachep); + return ret; +} + +void qlt_exit(void) +{ + if (!QLA_TGT_MODE_ENABLED()) + return; + + destroy_workqueue(qla_tgt_wq); + mempool_destroy(qla_tgt_mgmt_cmd_mempool); + kmem_cache_destroy(qla_tgt_mgmt_cmd_cachep); + kmem_cache_destroy(qla_tgt_cmd_cachep); +} diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h new file mode 100644 index 00000000000..9ec19bc2f0f --- /dev/null +++ b/drivers/scsi/qla2xxx/qla_target.h @@ -0,0 +1,1005 @@ +/* + * Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst@vlnb.net> + * Copyright (C) 2004 - 2005 Leonid Stoljar + * Copyright (C) 2006 Nathaniel Clark <nate@misrule.us> + * Copyright (C) 2007 - 2010 ID7 Ltd. + * + * Forward port and refactoring to modern qla2xxx and target/configfs + * + * Copyright (C) 2010-2011 Nicholas A. Bellinger <nab@kernel.org> + * + * Additional file for the target driver support. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * This is the global def file that is useful for including from the + * target portion. + */ + +#ifndef __QLA_TARGET_H +#define __QLA_TARGET_H + +#include "qla_def.h" + +/* + * Must be changed on any change in any initiator visible interfaces or + * data in the target add-on + */ +#define QLA2XXX_TARGET_MAGIC 269 + +/* + * Must be changed on any change in any target visible interfaces or + * data in the initiator + */ +#define QLA2XXX_INITIATOR_MAGIC 57222 + +#define QLA2XXX_INI_MODE_STR_EXCLUSIVE "exclusive" +#define QLA2XXX_INI_MODE_STR_DISABLED "disabled" +#define QLA2XXX_INI_MODE_STR_ENABLED "enabled" + +#define QLA2XXX_INI_MODE_EXCLUSIVE 0 +#define QLA2XXX_INI_MODE_DISABLED 1 +#define QLA2XXX_INI_MODE_ENABLED 2 + +#define QLA2XXX_COMMAND_COUNT_INIT 250 +#define QLA2XXX_IMMED_NOTIFY_COUNT_INIT 250 + +/* + * Used to mark which completion handles (for RIO Status's) are for CTIO's + * vs. regular (non-target) info. This is checked for in + * qla2x00_process_response_queue() to see if a handle coming back in a + * multi-complete should come to the tgt driver or be handled there by qla2xxx + */ +#define CTIO_COMPLETION_HANDLE_MARK BIT_29 +#if (CTIO_COMPLETION_HANDLE_MARK <= MAX_OUTSTANDING_COMMANDS) +#error "CTIO_COMPLETION_HANDLE_MARK not larger than MAX_OUTSTANDING_COMMANDS" +#endif +#define HANDLE_IS_CTIO_COMP(h) (h & CTIO_COMPLETION_HANDLE_MARK) + +/* Used to mark CTIO as intermediate */ +#define CTIO_INTERMEDIATE_HANDLE_MARK BIT_30 + +#ifndef OF_SS_MODE_0 +/* + * ISP target entries - Flags bit definitions. + */ +#define OF_SS_MODE_0 0 +#define OF_SS_MODE_1 1 +#define OF_SS_MODE_2 2 +#define OF_SS_MODE_3 3 + +#define OF_EXPL_CONF BIT_5 /* Explicit Confirmation Requested */ +#define OF_DATA_IN BIT_6 /* Data in to initiator */ + /* (data from target to initiator) */ +#define OF_DATA_OUT BIT_7 /* Data out from initiator */ + /* (data from initiator to target) */ +#define OF_NO_DATA (BIT_7 | BIT_6) +#define OF_INC_RC BIT_8 /* Increment command resource count */ +#define OF_FAST_POST BIT_9 /* Enable mailbox fast posting. */ +#define OF_CONF_REQ BIT_13 /* Confirmation Requested */ +#define OF_TERM_EXCH BIT_14 /* Terminate exchange */ +#define OF_SSTS BIT_15 /* Send SCSI status */ +#endif + +#ifndef QLA_TGT_DATASEGS_PER_CMD32 +#define QLA_TGT_DATASEGS_PER_CMD32 3 +#define QLA_TGT_DATASEGS_PER_CONT32 7 +#define QLA_TGT_MAX_SG32(ql) \ + (((ql) > 0) ? (QLA_TGT_DATASEGS_PER_CMD32 + \ + QLA_TGT_DATASEGS_PER_CONT32*((ql) - 1)) : 0) + +#define QLA_TGT_DATASEGS_PER_CMD64 2 +#define QLA_TGT_DATASEGS_PER_CONT64 5 +#define QLA_TGT_MAX_SG64(ql) \ + (((ql) > 0) ? (QLA_TGT_DATASEGS_PER_CMD64 + \ + QLA_TGT_DATASEGS_PER_CONT64*((ql) - 1)) : 0) +#endif + +#ifndef QLA_TGT_DATASEGS_PER_CMD_24XX +#define QLA_TGT_DATASEGS_PER_CMD_24XX 1 +#define QLA_TGT_DATASEGS_PER_CONT_24XX 5 +#define QLA_TGT_MAX_SG_24XX(ql) \ + (min(1270, ((ql) > 0) ? (QLA_TGT_DATASEGS_PER_CMD_24XX + \ + QLA_TGT_DATASEGS_PER_CONT_24XX*((ql) - 1)) : 0)) +#endif +#endif + +#define GET_TARGET_ID(ha, iocb) ((HAS_EXTENDED_IDS(ha)) \ + ? le16_to_cpu((iocb)->u.isp2x.target.extended) \ + : (uint16_t)(iocb)->u.isp2x.target.id.standard) + +#ifndef IMMED_NOTIFY_TYPE +#define IMMED_NOTIFY_TYPE 0x0D /* Immediate notify entry. */ +/* + * ISP queue - immediate notify entry structure definition. + * This is sent by the ISP to the Target driver. + * This IOCB would have report of events sent by the + * initiator, that needs to be handled by the target + * driver immediately. + */ +struct imm_ntfy_from_isp { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + union { + struct { + uint32_t sys_define_2; /* System defined. */ + target_id_t target; + uint16_t lun; + uint8_t target_id; + uint8_t reserved_1; + uint16_t status_modifier; + uint16_t status; + uint16_t task_flags; + uint16_t seq_id; + uint16_t srr_rx_id; + uint32_t srr_rel_offs; + uint16_t srr_ui; +#define SRR_IU_DATA_IN 0x1 +#define SRR_IU_DATA_OUT 0x5 +#define SRR_IU_STATUS 0x7 + uint16_t srr_ox_id; + uint8_t reserved_2[28]; + } isp2x; + struct { + uint32_t reserved; + uint16_t nport_handle; + uint16_t reserved_2; + uint16_t flags; +#define NOTIFY24XX_FLAGS_GLOBAL_TPRLO BIT_1 +#define NOTIFY24XX_FLAGS_PUREX_IOCB BIT_0 + uint16_t srr_rx_id; + uint16_t status; + uint8_t status_subcode; + uint8_t reserved_3; + uint32_t exchange_address; + uint32_t srr_rel_offs; + uint16_t srr_ui; + uint16_t srr_ox_id; + uint8_t reserved_4[19]; + uint8_t vp_index; + uint32_t reserved_5; + uint8_t port_id[3]; + uint8_t reserved_6; + } isp24; + } u; + uint16_t reserved_7; + uint16_t ox_id; +} __packed; +#endif + +#ifndef NOTIFY_ACK_TYPE +#define NOTIFY_ACK_TYPE 0x0E /* Notify acknowledge entry. */ +/* + * ISP queue - notify acknowledge entry structure definition. + * This is sent to the ISP from the target driver. + */ +struct nack_to_isp { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + union { + struct { + uint32_t sys_define_2; /* System defined. */ + target_id_t target; + uint8_t target_id; + uint8_t reserved_1; + uint16_t flags; + uint16_t resp_code; + uint16_t status; + uint16_t task_flags; + uint16_t seq_id; + uint16_t srr_rx_id; + uint32_t srr_rel_offs; + uint16_t srr_ui; + uint16_t srr_flags; + uint16_t srr_reject_code; + uint8_t srr_reject_vendor_uniq; + uint8_t srr_reject_code_expl; + uint8_t reserved_2[24]; + } isp2x; + struct { + uint32_t handle; + uint16_t nport_handle; + uint16_t reserved_1; + uint16_t flags; + uint16_t srr_rx_id; + uint16_t status; + uint8_t status_subcode; + uint8_t reserved_3; + uint32_t exchange_address; + uint32_t srr_rel_offs; + uint16_t srr_ui; + uint16_t srr_flags; + uint8_t reserved_4[19]; + uint8_t vp_index; + uint8_t srr_reject_vendor_uniq; + uint8_t srr_reject_code_expl; + uint8_t srr_reject_code; + uint8_t reserved_5[5]; + } isp24; + } u; + uint8_t reserved[2]; + uint16_t ox_id; +} __packed; +#define NOTIFY_ACK_SRR_FLAGS_ACCEPT 0 +#define NOTIFY_ACK_SRR_FLAGS_REJECT 1 + +#define NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM 0x9 + +#define NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL 0 +#define NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_UNABLE_TO_SUPPLY_DATA 0x2a + +#define NOTIFY_ACK_SUCCESS 0x01 +#endif + +#ifndef ACCEPT_TGT_IO_TYPE +#define ACCEPT_TGT_IO_TYPE 0x16 /* Accept target I/O entry. */ +#endif + +#ifndef CONTINUE_TGT_IO_TYPE +#define CONTINUE_TGT_IO_TYPE 0x17 +/* + * ISP queue - Continue Target I/O (CTIO) entry for status mode 0 structure. + * This structure is sent to the ISP 2xxx from target driver. + */ +struct ctio_to_2xxx { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t handle; /* System defined handle */ + target_id_t target; + uint16_t rx_id; + uint16_t flags; + uint16_t status; + uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */ + uint16_t dseg_count; /* Data segment count. */ + uint32_t relative_offset; + uint32_t residual; + uint16_t reserved_1[3]; + uint16_t scsi_status; + uint32_t transfer_length; + uint32_t dseg_0_address; /* Data segment 0 address. */ + uint32_t dseg_0_length; /* Data segment 0 length. */ + uint32_t dseg_1_address; /* Data segment 1 address. */ + uint32_t dseg_1_length; /* Data segment 1 length. */ + uint32_t dseg_2_address; /* Data segment 2 address. */ + uint32_t dseg_2_length; /* Data segment 2 length. */ +} __packed; +#define ATIO_PATH_INVALID 0x07 +#define ATIO_CANT_PROV_CAP 0x16 +#define ATIO_CDB_VALID 0x3D + +#define ATIO_EXEC_READ BIT_1 +#define ATIO_EXEC_WRITE BIT_0 +#endif + +#ifndef CTIO_A64_TYPE +#define CTIO_A64_TYPE 0x1F +#define CTIO_SUCCESS 0x01 +#define CTIO_ABORTED 0x02 +#define CTIO_INVALID_RX_ID 0x08 +#define CTIO_TIMEOUT 0x0B +#define CTIO_LIP_RESET 0x0E +#define CTIO_TARGET_RESET 0x17 +#define CTIO_PORT_UNAVAILABLE 0x28 +#define CTIO_PORT_LOGGED_OUT 0x29 +#define CTIO_PORT_CONF_CHANGED 0x2A +#define CTIO_SRR_RECEIVED 0x45 +#endif + +#ifndef CTIO_RET_TYPE +#define CTIO_RET_TYPE 0x17 /* CTIO return entry */ +#define ATIO_TYPE7 0x06 /* Accept target I/O entry for 24xx */ + +struct fcp_hdr { + uint8_t r_ctl; + uint8_t d_id[3]; + uint8_t cs_ctl; + uint8_t s_id[3]; + uint8_t type; + uint8_t f_ctl[3]; + uint8_t seq_id; + uint8_t df_ctl; + uint16_t seq_cnt; + uint16_t ox_id; + uint16_t rx_id; + uint32_t parameter; +} __packed; + +struct fcp_hdr_le { + uint8_t d_id[3]; + uint8_t r_ctl; + uint8_t s_id[3]; + uint8_t cs_ctl; + uint8_t f_ctl[3]; + uint8_t type; + uint16_t seq_cnt; + uint8_t df_ctl; + uint8_t seq_id; + uint16_t rx_id; + uint16_t ox_id; + uint32_t parameter; +} __packed; + +#define F_CTL_EXCH_CONTEXT_RESP BIT_23 +#define F_CTL_SEQ_CONTEXT_RESIP BIT_22 +#define F_CTL_LAST_SEQ BIT_20 +#define F_CTL_END_SEQ BIT_19 +#define F_CTL_SEQ_INITIATIVE BIT_16 + +#define R_CTL_BASIC_LINK_SERV 0x80 +#define R_CTL_B_ACC 0x4 +#define R_CTL_B_RJT 0x5 + +struct atio7_fcp_cmnd { + uint64_t lun; + uint8_t cmnd_ref; + uint8_t task_attr:3; + uint8_t reserved:5; + uint8_t task_mgmt_flags; +#define FCP_CMND_TASK_MGMT_CLEAR_ACA 6 +#define FCP_CMND_TASK_MGMT_TARGET_RESET 5 +#define FCP_CMND_TASK_MGMT_LU_RESET 4 +#define FCP_CMND_TASK_MGMT_CLEAR_TASK_SET 2 +#define FCP_CMND_TASK_MGMT_ABORT_TASK_SET 1 + uint8_t wrdata:1; + uint8_t rddata:1; + uint8_t add_cdb_len:6; + uint8_t cdb[16]; + /* + * add_cdb is optional and can absent from struct atio7_fcp_cmnd. Size 4 + * only to make sizeof(struct atio7_fcp_cmnd) be as expected by + * BUILD_BUG_ON in qlt_init(). + */ + uint8_t add_cdb[4]; + /* uint32_t data_length; */ +} __packed; + +/* + * ISP queue - Accept Target I/O (ATIO) type entry IOCB structure. + * This is sent from the ISP to the target driver. + */ +struct atio_from_isp { + union { + struct { + uint16_t entry_hdr; + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t sys_define_2; /* System defined. */ + target_id_t target; + uint16_t rx_id; + uint16_t flags; + uint16_t status; + uint8_t command_ref; + uint8_t task_codes; + uint8_t task_flags; + uint8_t execution_codes; + uint8_t cdb[MAX_CMDSZ]; + uint32_t data_length; + uint16_t lun; + uint8_t initiator_port_name[WWN_SIZE]; /* on qla23xx */ + uint16_t reserved_32[6]; + uint16_t ox_id; + } isp2x; + struct { + uint16_t entry_hdr; + uint8_t fcp_cmnd_len_low; + uint8_t fcp_cmnd_len_high:4; + uint8_t attr:4; + uint32_t exchange_addr; +#define ATIO_EXCHANGE_ADDRESS_UNKNOWN 0xFFFFFFFF + struct fcp_hdr fcp_hdr; + struct atio7_fcp_cmnd fcp_cmnd; + } isp24; + struct { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t data[58]; + uint32_t signature; +#define ATIO_PROCESSED 0xDEADDEAD /* Signature */ + } raw; + } u; +} __packed; + +#define CTIO_TYPE7 0x12 /* Continue target I/O entry (for 24xx) */ + +/* + * ISP queue - Continue Target I/O (ATIO) type 7 entry (for 24xx) structure. + * This structure is sent to the ISP 24xx from the target driver. + */ + +struct ctio7_to_24xx { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t handle; /* System defined handle */ + uint16_t nport_handle; +#define CTIO7_NHANDLE_UNRECOGNIZED 0xFFFF + uint16_t timeout; + uint16_t dseg_count; /* Data segment count. */ + uint8_t vp_index; + uint8_t add_flags; + uint8_t initiator_id[3]; + uint8_t reserved; + uint32_t exchange_addr; + union { + struct { + uint16_t reserved1; + uint16_t flags; + uint32_t residual; + uint16_t ox_id; + uint16_t scsi_status; + uint32_t relative_offset; + uint32_t reserved2; + uint32_t transfer_length; + uint32_t reserved3; + /* Data segment 0 address. */ + uint32_t dseg_0_address[2]; + /* Data segment 0 length. */ + uint32_t dseg_0_length; + } status0; + struct { + uint16_t sense_length; + uint16_t flags; + uint32_t residual; + uint16_t ox_id; + uint16_t scsi_status; + uint16_t response_len; + uint16_t reserved; + uint8_t sense_data[24]; + } status1; + } u; +} __packed; + +/* + * ISP queue - CTIO type 7 from ISP 24xx to target driver + * returned entry structure. + */ +struct ctio7_from_24xx { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t handle; /* System defined handle */ + uint16_t status; + uint16_t timeout; + uint16_t dseg_count; /* Data segment count. */ + uint8_t vp_index; + uint8_t reserved1[5]; + uint32_t exchange_address; + uint16_t reserved2; + uint16_t flags; + uint32_t residual; + uint16_t ox_id; + uint16_t reserved3; + uint32_t relative_offset; + uint8_t reserved4[24]; +} __packed; + +/* CTIO7 flags values */ +#define CTIO7_FLAGS_SEND_STATUS BIT_15 +#define CTIO7_FLAGS_TERMINATE BIT_14 +#define CTIO7_FLAGS_CONFORM_REQ BIT_13 +#define CTIO7_FLAGS_DONT_RET_CTIO BIT_8 +#define CTIO7_FLAGS_STATUS_MODE_0 0 +#define CTIO7_FLAGS_STATUS_MODE_1 BIT_6 +#define CTIO7_FLAGS_EXPLICIT_CONFORM BIT_5 +#define CTIO7_FLAGS_CONFIRM_SATISF BIT_4 +#define CTIO7_FLAGS_DSD_PTR BIT_2 +#define CTIO7_FLAGS_DATA_IN BIT_1 +#define CTIO7_FLAGS_DATA_OUT BIT_0 + +#define ELS_PLOGI 0x3 +#define ELS_FLOGI 0x4 +#define ELS_LOGO 0x5 +#define ELS_PRLI 0x20 +#define ELS_PRLO 0x21 +#define ELS_TPRLO 0x24 +#define ELS_PDISC 0x50 +#define ELS_ADISC 0x52 + +/* + * ISP queue - ABTS received/response entries structure definition for 24xx. + */ +#define ABTS_RECV_24XX 0x54 /* ABTS received (for 24xx) */ +#define ABTS_RESP_24XX 0x55 /* ABTS responce (for 24xx) */ + +/* + * ISP queue - ABTS received IOCB entry structure definition for 24xx. + * The ABTS BLS received from the wire is sent to the + * target driver by the ISP 24xx. + * The IOCB is placed on the response queue. + */ +struct abts_recv_from_24xx { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint8_t reserved_1[6]; + uint16_t nport_handle; + uint8_t reserved_2[2]; + uint8_t vp_index; + uint8_t reserved_3:4; + uint8_t sof_type:4; + uint32_t exchange_address; + struct fcp_hdr_le fcp_hdr_le; + uint8_t reserved_4[16]; + uint32_t exchange_addr_to_abort; +} __packed; + +#define ABTS_PARAM_ABORT_SEQ BIT_0 + +struct ba_acc_le { + uint16_t reserved; + uint8_t seq_id_last; + uint8_t seq_id_valid; +#define SEQ_ID_VALID 0x80 +#define SEQ_ID_INVALID 0x00 + uint16_t rx_id; + uint16_t ox_id; + uint16_t high_seq_cnt; + uint16_t low_seq_cnt; +} __packed; + +struct ba_rjt_le { + uint8_t vendor_uniq; + uint8_t reason_expl; + uint8_t reason_code; +#define BA_RJT_REASON_CODE_INVALID_COMMAND 0x1 +#define BA_RJT_REASON_CODE_UNABLE_TO_PERFORM 0x9 + uint8_t reserved; +} __packed; + +/* + * ISP queue - ABTS Response IOCB entry structure definition for 24xx. + * The ABTS response to the ABTS received is sent by the + * target driver to the ISP 24xx. + * The IOCB is placed on the request queue. + */ +struct abts_resp_to_24xx { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t handle; + uint16_t reserved_1; + uint16_t nport_handle; + uint16_t control_flags; +#define ABTS_CONTR_FLG_TERM_EXCHG BIT_0 + uint8_t vp_index; + uint8_t reserved_3:4; + uint8_t sof_type:4; + uint32_t exchange_address; + struct fcp_hdr_le fcp_hdr_le; + union { + struct ba_acc_le ba_acct; + struct ba_rjt_le ba_rjt; + } __packed payload; + uint32_t reserved_4; + uint32_t exchange_addr_to_abort; +} __packed; + +/* + * ISP queue - ABTS Response IOCB from ISP24xx Firmware entry structure. + * The ABTS response with completion status to the ABTS response + * (sent by the target driver to the ISP 24xx) is sent by the + * ISP24xx firmware to the target driver. + * The IOCB is placed on the response queue. + */ +struct abts_resp_from_24xx_fw { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + uint32_t handle; + uint16_t compl_status; +#define ABTS_RESP_COMPL_SUCCESS 0 +#define ABTS_RESP_COMPL_SUBCODE_ERROR 0x31 + uint16_t nport_handle; + uint16_t reserved_1; + uint8_t reserved_2; + uint8_t reserved_3:4; + uint8_t sof_type:4; + uint32_t exchange_address; + struct fcp_hdr_le fcp_hdr_le; + uint8_t reserved_4[8]; + uint32_t error_subcode1; +#define ABTS_RESP_SUBCODE_ERR_ABORTED_EXCH_NOT_TERM 0x1E + uint32_t error_subcode2; + uint32_t exchange_addr_to_abort; +} __packed; + +/********************************************************************\ + * Type Definitions used by initiator & target halves +\********************************************************************/ + +struct qla_tgt_mgmt_cmd; +struct qla_tgt_sess; + +/* + * This structure provides a template of function calls that the + * target driver (from within qla_target.c) can issue to the + * target module (tcm_qla2xxx). + */ +struct qla_tgt_func_tmpl { + + int (*handle_cmd)(struct scsi_qla_host *, struct qla_tgt_cmd *, + unsigned char *, uint32_t, int, int, int); + int (*handle_data)(struct qla_tgt_cmd *); + int (*handle_tmr)(struct qla_tgt_mgmt_cmd *, uint32_t, uint8_t, + uint32_t); + void (*free_cmd)(struct qla_tgt_cmd *); + void (*free_mcmd)(struct qla_tgt_mgmt_cmd *); + void (*free_session)(struct qla_tgt_sess *); + + int (*check_initiator_node_acl)(struct scsi_qla_host *, unsigned char *, + void *, uint8_t *, uint16_t); + struct qla_tgt_sess *(*find_sess_by_loop_id)(struct scsi_qla_host *, + const uint16_t); + struct qla_tgt_sess *(*find_sess_by_s_id)(struct scsi_qla_host *, + const uint8_t *); + void (*clear_nacl_from_fcport_map)(struct qla_tgt_sess *); + void (*put_sess)(struct qla_tgt_sess *); + void (*shutdown_sess)(struct qla_tgt_sess *); +}; + +int qla2x00_wait_for_hba_online(struct scsi_qla_host *); + +#include <target/target_core_base.h> + +#define QLA_TGT_TIMEOUT 10 /* in seconds */ + +#define QLA_TGT_MAX_HW_PENDING_TIME 60 /* in seconds */ + +/* Immediate notify status constants */ +#define IMM_NTFY_LIP_RESET 0x000E +#define IMM_NTFY_LIP_LINK_REINIT 0x000F +#define IMM_NTFY_IOCB_OVERFLOW 0x0016 +#define IMM_NTFY_ABORT_TASK 0x0020 +#define IMM_NTFY_PORT_LOGOUT 0x0029 +#define IMM_NTFY_PORT_CONFIG 0x002A +#define IMM_NTFY_GLBL_TPRLO 0x002D +#define IMM_NTFY_GLBL_LOGO 0x002E +#define IMM_NTFY_RESOURCE 0x0034 +#define IMM_NTFY_MSG_RX 0x0036 +#define IMM_NTFY_SRR 0x0045 +#define IMM_NTFY_ELS 0x0046 + +/* Immediate notify task flags */ +#define IMM_NTFY_TASK_MGMT_SHIFT 8 + +#define QLA_TGT_CLEAR_ACA 0x40 +#define QLA_TGT_TARGET_RESET 0x20 +#define QLA_TGT_LUN_RESET 0x10 +#define QLA_TGT_CLEAR_TS 0x04 +#define QLA_TGT_ABORT_TS 0x02 +#define QLA_TGT_ABORT_ALL_SESS 0xFFFF +#define QLA_TGT_ABORT_ALL 0xFFFE +#define QLA_TGT_NEXUS_LOSS_SESS 0xFFFD +#define QLA_TGT_NEXUS_LOSS 0xFFFC + +/* Notify Acknowledge flags */ +#define NOTIFY_ACK_RES_COUNT BIT_8 +#define NOTIFY_ACK_CLEAR_LIP_RESET BIT_5 +#define NOTIFY_ACK_TM_RESP_CODE_VALID BIT_4 + +/* Command's states */ +#define QLA_TGT_STATE_NEW 0 /* New command + target processing */ +#define QLA_TGT_STATE_NEED_DATA 1 /* target needs data to continue */ +#define QLA_TGT_STATE_DATA_IN 2 /* Data arrived + target processing */ +#define QLA_TGT_STATE_PROCESSED 3 /* target done processing */ +#define QLA_TGT_STATE_ABORTED 4 /* Command aborted */ + +/* Special handles */ +#define QLA_TGT_NULL_HANDLE 0 +#define QLA_TGT_SKIP_HANDLE (0xFFFFFFFF & ~CTIO_COMPLETION_HANDLE_MARK) + +/* ATIO task_codes field */ +#define ATIO_SIMPLE_QUEUE 0 +#define ATIO_HEAD_OF_QUEUE 1 +#define ATIO_ORDERED_QUEUE 2 +#define ATIO_ACA_QUEUE 4 +#define ATIO_UNTAGGED 5 + +/* TM failed response codes, see FCP (9.4.11 FCP_RSP_INFO) */ +#define FC_TM_SUCCESS 0 +#define FC_TM_BAD_FCP_DATA 1 +#define FC_TM_BAD_CMD 2 +#define FC_TM_FCP_DATA_MISMATCH 3 +#define FC_TM_REJECT 4 +#define FC_TM_FAILED 5 + +/* + * Error code of qlt_pre_xmit_response() meaning that cmd's exchange was + * terminated, so no more actions is needed and success should be returned + * to target. + */ +#define QLA_TGT_PRE_XMIT_RESP_CMD_ABORTED 0x1717 + +#if (BITS_PER_LONG > 32) || defined(CONFIG_HIGHMEM64G) +#define pci_dma_lo32(a) (a & 0xffffffff) +#define pci_dma_hi32(a) ((((a) >> 16)>>16) & 0xffffffff) +#else +#define pci_dma_lo32(a) (a & 0xffffffff) +#define pci_dma_hi32(a) 0 +#endif + +#define QLA_TGT_SENSE_VALID(sense) ((sense != NULL) && \ + (((const uint8_t *)(sense))[0] & 0x70) == 0x70) + +struct qla_port_24xx_data { + uint8_t port_name[WWN_SIZE]; + uint16_t loop_id; + uint16_t reserved; +}; + +struct qla_tgt { + struct scsi_qla_host *vha; + struct qla_hw_data *ha; + + /* + * To sync between IRQ handlers and qlt_target_release(). Needed, + * because req_pkt() can drop/reaquire HW lock inside. Protected by + * HW lock. + */ + int irq_cmd_count; + + int datasegs_per_cmd, datasegs_per_cont, sg_tablesize; + + /* Target's flags, serialized by pha->hardware_lock */ + unsigned int tgt_enable_64bit_addr:1; /* 64-bits PCI addr enabled */ + unsigned int link_reinit_iocb_pending:1; + + /* + * Protected by tgt_mutex AND hardware_lock for writing and tgt_mutex + * OR hardware_lock for reading. + */ + int tgt_stop; /* the target mode driver is being stopped */ + int tgt_stopped; /* the target mode driver has been stopped */ + + /* Count of sessions refering qla_tgt. Protected by hardware_lock. */ + int sess_count; + + /* Protected by hardware_lock. Addition also protected by tgt_mutex. */ + struct list_head sess_list; + + /* Protected by hardware_lock */ + struct list_head del_sess_list; + struct delayed_work sess_del_work; + + spinlock_t sess_work_lock; + struct list_head sess_works_list; + struct work_struct sess_work; + + struct imm_ntfy_from_isp link_reinit_iocb; + wait_queue_head_t waitQ; + int notify_ack_expected; + int abts_resp_expected; + int modify_lun_expected; + + int ctio_srr_id; + int imm_srr_id; + spinlock_t srr_lock; + struct list_head srr_ctio_list; + struct list_head srr_imm_list; + struct work_struct srr_work; + + atomic_t tgt_global_resets_count; + + struct list_head tgt_list_entry; +}; + +/* + * Equivilant to IT Nexus (Initiator-Target) + */ +struct qla_tgt_sess { + uint16_t loop_id; + port_id_t s_id; + + unsigned int conf_compl_supported:1; + unsigned int deleted:1; + unsigned int local:1; + unsigned int tearing_down:1; + + struct se_session *se_sess; + struct scsi_qla_host *vha; + struct qla_tgt *tgt; + + struct list_head sess_list_entry; + unsigned long expires; + struct list_head del_list_entry; + + uint8_t port_name[WWN_SIZE]; + struct work_struct free_work; +}; + +struct qla_tgt_cmd { + struct qla_tgt_sess *sess; + int state; + struct se_cmd se_cmd; + struct work_struct free_work; + struct work_struct work; + /* Sense buffer that will be mapped into outgoing status */ + unsigned char sense_buffer[TRANSPORT_SENSE_BUFFER]; + + /* to save extra sess dereferences */ + unsigned int conf_compl_supported:1; + unsigned int sg_mapped:1; + unsigned int free_sg:1; + unsigned int aborted:1; /* Needed in case of SRR */ + unsigned int write_data_transferred:1; + + struct scatterlist *sg; /* cmd data buffer SG vector */ + int sg_cnt; /* SG segments count */ + int bufflen; /* cmd buffer length */ + int offset; + uint32_t tag; + uint32_t unpacked_lun; + enum dma_data_direction dma_data_direction; + + uint16_t loop_id; /* to save extra sess dereferences */ + struct qla_tgt *tgt; /* to save extra sess dereferences */ + struct scsi_qla_host *vha; + struct list_head cmd_list; + + struct atio_from_isp atio; +}; + +struct qla_tgt_sess_work_param { + struct list_head sess_works_list_entry; + +#define QLA_TGT_SESS_WORK_ABORT 1 +#define QLA_TGT_SESS_WORK_TM 2 + int type; + + union { + struct abts_recv_from_24xx abts; + struct imm_ntfy_from_isp tm_iocb; + struct atio_from_isp tm_iocb2; + }; +}; + +struct qla_tgt_mgmt_cmd { + uint8_t tmr_func; + uint8_t fc_tm_rsp; + struct qla_tgt_sess *sess; + struct se_cmd se_cmd; + struct work_struct free_work; + unsigned int flags; +#define QLA24XX_MGMT_SEND_NACK 1 + union { + struct atio_from_isp atio; + struct imm_ntfy_from_isp imm_ntfy; + struct abts_recv_from_24xx abts; + } __packed orig_iocb; +}; + +struct qla_tgt_prm { + struct qla_tgt_cmd *cmd; + struct qla_tgt *tgt; + void *pkt; + struct scatterlist *sg; /* cmd data buffer SG vector */ + int seg_cnt; + int req_cnt; + uint16_t rq_result; + uint16_t scsi_status; + unsigned char *sense_buffer; + int sense_buffer_len; + int residual; + int add_status_pkt; +}; + +struct qla_tgt_srr_imm { + struct list_head srr_list_entry; + int srr_id; + struct imm_ntfy_from_isp imm_ntfy; +}; + +struct qla_tgt_srr_ctio { + struct list_head srr_list_entry; + int srr_id; + struct qla_tgt_cmd *cmd; +}; + +#define QLA_TGT_XMIT_DATA 1 +#define QLA_TGT_XMIT_STATUS 2 +#define QLA_TGT_XMIT_ALL (QLA_TGT_XMIT_STATUS|QLA_TGT_XMIT_DATA) + +#include <linux/version.h> + +extern struct qla_tgt_data qla_target; +/* + * Internal function prototypes + */ +void qlt_disable_vha(struct scsi_qla_host *); + +/* + * Function prototypes for qla_target.c logic used by qla2xxx LLD code. + */ +extern int qlt_add_target(struct qla_hw_data *, struct scsi_qla_host *); +extern int qlt_remove_target(struct qla_hw_data *, struct scsi_qla_host *); +extern int qlt_lport_register(struct qla_tgt_func_tmpl *, u64, + int (*callback)(struct scsi_qla_host *), void *); +extern void qlt_lport_deregister(struct scsi_qla_host *); +extern void qlt_unreg_sess(struct qla_tgt_sess *); +extern void qlt_fc_port_added(struct scsi_qla_host *, fc_port_t *); +extern void qlt_fc_port_deleted(struct scsi_qla_host *, fc_port_t *); +extern void qlt_set_mode(struct scsi_qla_host *ha); +extern void qlt_clear_mode(struct scsi_qla_host *ha); +extern int __init qlt_init(void); +extern void qlt_exit(void); +extern void qlt_update_vp_map(struct scsi_qla_host *, int); + +/* + * This macro is used during early initializations when host->active_mode + * is not set. Right now, ha value is ignored. + */ +#define QLA_TGT_MODE_ENABLED() (ql2x_ini_mode != QLA2XXX_INI_MODE_ENABLED) + +static inline bool qla_tgt_mode_enabled(struct scsi_qla_host *ha) +{ + return ha->host->active_mode & MODE_TARGET; +} + +static inline bool qla_ini_mode_enabled(struct scsi_qla_host *ha) +{ + return ha->host->active_mode & MODE_INITIATOR; +} + +static inline void qla_reverse_ini_mode(struct scsi_qla_host *ha) +{ + if (ha->host->active_mode & MODE_INITIATOR) + ha->host->active_mode &= ~MODE_INITIATOR; + else + ha->host->active_mode |= MODE_INITIATOR; +} + +/* + * Exported symbols from qla_target.c LLD logic used by qla2xxx code.. + */ +extern void qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *, + struct atio_from_isp *); +extern void qlt_response_pkt_all_vps(struct scsi_qla_host *, response_t *); +extern int qlt_rdy_to_xfer(struct qla_tgt_cmd *); +extern int qlt_xmit_response(struct qla_tgt_cmd *, int, uint8_t); +extern void qlt_xmit_tm_rsp(struct qla_tgt_mgmt_cmd *); +extern void qlt_free_mcmd(struct qla_tgt_mgmt_cmd *); +extern void qlt_free_cmd(struct qla_tgt_cmd *cmd); +extern void qlt_ctio_completion(struct scsi_qla_host *, uint32_t); +extern void qlt_async_event(uint16_t, struct scsi_qla_host *, uint16_t *); +extern void qlt_enable_vha(struct scsi_qla_host *); +extern void qlt_vport_create(struct scsi_qla_host *, struct qla_hw_data *); +extern void qlt_rff_id(struct scsi_qla_host *, struct ct_sns_req *); +extern void qlt_init_atio_q_entries(struct scsi_qla_host *); +extern void qlt_24xx_process_atio_queue(struct scsi_qla_host *); +extern void qlt_24xx_config_rings(struct scsi_qla_host *, + device_reg_t __iomem *); +extern void qlt_24xx_config_nvram_stage1(struct scsi_qla_host *, + struct nvram_24xx *); +extern void qlt_24xx_config_nvram_stage2(struct scsi_qla_host *, + struct init_cb_24xx *); +extern int qlt_24xx_process_response_error(struct scsi_qla_host *, + struct sts_entry_24xx *); +extern void qlt_modify_vp_config(struct scsi_qla_host *, + struct vp_config_entry_24xx *); +extern void qlt_probe_one_stage1(struct scsi_qla_host *, struct qla_hw_data *); +extern int qlt_mem_alloc(struct qla_hw_data *); +extern void qlt_mem_free(struct qla_hw_data *); +extern void qlt_stop_phase1(struct qla_tgt *); +extern void qlt_stop_phase2(struct qla_tgt *); + +#endif /* __QLA_TARGET_H */ diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.c b/drivers/scsi/qla2xxx/tcm_qla2xxx.c new file mode 100644 index 00000000000..436598f5740 --- /dev/null +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.c @@ -0,0 +1,1955 @@ +/******************************************************************************* + * This file contains tcm implementation using v4 configfs fabric infrastructure + * for QLogic target mode HBAs + * + * ?? Copyright 2010-2011 RisingTide Systems LLC. + * + * Licensed to the Linux Foundation under the General Public License (GPL) + * version 2. + * + * Author: Nicholas A. Bellinger <nab@risingtidesystems.com> + * + * tcm_qla2xxx_parse_wwn() and tcm_qla2xxx_format_wwn() contains code from + * the TCM_FC / Open-FCoE.org fabric module. + * + * Copyright (c) 2010 Cisco Systems, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + ****************************************************************************/ + + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <generated/utsrelease.h> +#include <linux/utsname.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "qla_def.h" +#include "qla_target.h" +#include "tcm_qla2xxx.h" + +struct workqueue_struct *tcm_qla2xxx_free_wq; +struct workqueue_struct *tcm_qla2xxx_cmd_wq; + +static int tcm_qla2xxx_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int tcm_qla2xxx_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +/* + * Parse WWN. + * If strict, we require lower-case hex and colon separators to be sure + * the name is the same as what would be generated by ft_format_wwn() + * so the name and wwn are mapped one-to-one. + */ +static ssize_t tcm_qla2xxx_parse_wwn(const char *name, u64 *wwn, int strict) +{ + const char *cp; + char c; + u32 nibble; + u32 byte = 0; + u32 pos = 0; + u32 err; + + *wwn = 0; + for (cp = name; cp < &name[TCM_QLA2XXX_NAMELEN - 1]; cp++) { + c = *cp; + if (c == '\n' && cp[1] == '\0') + continue; + if (strict && pos++ == 2 && byte++ < 7) { + pos = 0; + if (c == ':') + continue; + err = 1; + goto fail; + } + if (c == '\0') { + err = 2; + if (strict && byte != 8) + goto fail; + return cp - name; + } + err = 3; + if (isdigit(c)) + nibble = c - '0'; + else if (isxdigit(c) && (islower(c) || !strict)) + nibble = tolower(c) - 'a' + 10; + else + goto fail; + *wwn = (*wwn << 4) | nibble; + } + err = 4; +fail: + pr_debug("err %u len %zu pos %u byte %u\n", + err, cp - name, pos, byte); + return -1; +} + +static ssize_t tcm_qla2xxx_format_wwn(char *buf, size_t len, u64 wwn) +{ + u8 b[8]; + + put_unaligned_be64(wwn, b); + return snprintf(buf, len, + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); +} + +static char *tcm_qla2xxx_get_fabric_name(void) +{ + return "qla2xxx"; +} + +/* + * From drivers/scsi/scsi_transport_fc.c:fc_parse_wwn + */ +static int tcm_qla2xxx_npiv_extract_wwn(const char *ns, u64 *nm) +{ + unsigned int i, j, value; + u8 wwn[8]; + + memset(wwn, 0, sizeof(wwn)); + + /* Validate and store the new name */ + for (i = 0, j = 0; i < 16; i++) { + value = hex_to_bin(*ns++); + if (value >= 0) + j = (j << 4) | value; + else + return -EINVAL; + + if (i % 2) { + wwn[i/2] = j & 0xff; + j = 0; + } + } + + *nm = wwn_to_u64(wwn); + return 0; +} + +/* + * This parsing logic follows drivers/scsi/scsi_transport_fc.c: + * store_fc_host_vport_create() + */ +static int tcm_qla2xxx_npiv_parse_wwn( + const char *name, + size_t count, + u64 *wwpn, + u64 *wwnn) +{ + unsigned int cnt = count; + int rc; + + *wwpn = 0; + *wwnn = 0; + + /* count may include a LF at end of string */ + if (name[cnt-1] == '\n') + cnt--; + + /* validate we have enough characters for WWPN */ + if ((cnt != (16+1+16)) || (name[16] != ':')) + return -EINVAL; + + rc = tcm_qla2xxx_npiv_extract_wwn(&name[0], wwpn); + if (rc != 0) + return rc; + + rc = tcm_qla2xxx_npiv_extract_wwn(&name[17], wwnn); + if (rc != 0) + return rc; + + return 0; +} + +static ssize_t tcm_qla2xxx_npiv_format_wwn(char *buf, size_t len, + u64 wwpn, u64 wwnn) +{ + u8 b[8], b2[8]; + + put_unaligned_be64(wwpn, b); + put_unaligned_be64(wwnn, b2); + return snprintf(buf, len, + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x," + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b2[0], b2[1], b2[2], b2[3], b2[4], b2[5], b2[6], b2[7]); +} + +static char *tcm_qla2xxx_npiv_get_fabric_name(void) +{ + return "qla2xxx_npiv"; +} + +static u8 tcm_qla2xxx_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + u8 proto_id; + + switch (lport->lport_proto_id) { + case SCSI_PROTOCOL_FCP: + default: + proto_id = fc_get_fabric_proto_ident(se_tpg); + break; + } + + return proto_id; +} + +static char *tcm_qla2xxx_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + + return &lport->lport_name[0]; +} + +static char *tcm_qla2xxx_npiv_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + + return &lport->lport_npiv_name[0]; +} + +static u16 tcm_qla2xxx_get_tag(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + return tpg->lport_tpgt; +} + +static u32 tcm_qla2xxx_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +static u32 tcm_qla2xxx_get_pr_transport_id( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code, + unsigned char *buf) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + int ret = 0; + + switch (lport->lport_proto_id) { + case SCSI_PROTOCOL_FCP: + default: + ret = fc_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); + break; + } + + return ret; +} + +static u32 tcm_qla2xxx_get_pr_transport_id_len( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + int ret = 0; + + switch (lport->lport_proto_id) { + case SCSI_PROTOCOL_FCP: + default: + ret = fc_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); + break; + } + + return ret; +} + +static char *tcm_qla2xxx_parse_pr_out_transport_id( + struct se_portal_group *se_tpg, + const char *buf, + u32 *out_tid_len, + char **port_nexus_ptr) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + char *tid = NULL; + + switch (lport->lport_proto_id) { + case SCSI_PROTOCOL_FCP: + default: + tid = fc_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); + break; + } + + return tid; +} + +static int tcm_qla2xxx_check_demo_mode(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + + return QLA_TPG_ATTRIB(tpg)->generate_node_acls; +} + +static int tcm_qla2xxx_check_demo_mode_cache(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + + return QLA_TPG_ATTRIB(tpg)->cache_dynamic_acls; +} + +static int tcm_qla2xxx_check_demo_write_protect(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + + return QLA_TPG_ATTRIB(tpg)->demo_mode_write_protect; +} + +static int tcm_qla2xxx_check_prod_write_protect(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + + return QLA_TPG_ATTRIB(tpg)->prod_mode_write_protect; +} + +static struct se_node_acl *tcm_qla2xxx_alloc_fabric_acl( + struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_nacl *nacl; + + nacl = kzalloc(sizeof(struct tcm_qla2xxx_nacl), GFP_KERNEL); + if (!nacl) { + pr_err("Unable to alocate struct tcm_qla2xxx_nacl\n"); + return NULL; + } + + return &nacl->se_node_acl; +} + +static void tcm_qla2xxx_release_fabric_acl( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl) +{ + struct tcm_qla2xxx_nacl *nacl = container_of(se_nacl, + struct tcm_qla2xxx_nacl, se_node_acl); + kfree(nacl); +} + +static u32 tcm_qla2xxx_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + + return tpg->lport_tpgt; +} + +static void tcm_qla2xxx_complete_mcmd(struct work_struct *work) +{ + struct qla_tgt_mgmt_cmd *mcmd = container_of(work, + struct qla_tgt_mgmt_cmd, free_work); + + transport_generic_free_cmd(&mcmd->se_cmd, 0); +} + +/* + * Called from qla_target_template->free_mcmd(), and will call + * tcm_qla2xxx_release_cmd() via normal struct target_core_fabric_ops + * release callback. qla_hw_data->hardware_lock is expected to be held + */ +static void tcm_qla2xxx_free_mcmd(struct qla_tgt_mgmt_cmd *mcmd) +{ + INIT_WORK(&mcmd->free_work, tcm_qla2xxx_complete_mcmd); + queue_work(tcm_qla2xxx_free_wq, &mcmd->free_work); +} + +static void tcm_qla2xxx_complete_free(struct work_struct *work) +{ + struct qla_tgt_cmd *cmd = container_of(work, struct qla_tgt_cmd, work); + + transport_generic_free_cmd(&cmd->se_cmd, 0); +} + +/* + * Called from qla_target_template->free_cmd(), and will call + * tcm_qla2xxx_release_cmd via normal struct target_core_fabric_ops + * release callback. qla_hw_data->hardware_lock is expected to be held + */ +static void tcm_qla2xxx_free_cmd(struct qla_tgt_cmd *cmd) +{ + INIT_WORK(&cmd->work, tcm_qla2xxx_complete_free); + queue_work(tcm_qla2xxx_free_wq, &cmd->work); +} + +/* + * Called from struct target_core_fabric_ops->check_stop_free() context + */ +static int tcm_qla2xxx_check_stop_free(struct se_cmd *se_cmd) +{ + return target_put_sess_cmd(se_cmd->se_sess, se_cmd); +} + +/* tcm_qla2xxx_release_cmd - Callback from TCM Core to release underlying + * fabric descriptor @se_cmd command to release + */ +static void tcm_qla2xxx_release_cmd(struct se_cmd *se_cmd) +{ + struct qla_tgt_cmd *cmd; + + if (se_cmd->se_cmd_flags & SCF_SCSI_TMR_CDB) { + struct qla_tgt_mgmt_cmd *mcmd = container_of(se_cmd, + struct qla_tgt_mgmt_cmd, se_cmd); + qlt_free_mcmd(mcmd); + return; + } + + cmd = container_of(se_cmd, struct qla_tgt_cmd, se_cmd); + qlt_free_cmd(cmd); +} + +static int tcm_qla2xxx_shutdown_session(struct se_session *se_sess) +{ + struct qla_tgt_sess *sess = se_sess->fabric_sess_ptr; + struct scsi_qla_host *vha; + unsigned long flags; + + BUG_ON(!sess); + vha = sess->vha; + + spin_lock_irqsave(&vha->hw->hardware_lock, flags); + sess->tearing_down = 1; + target_splice_sess_cmd_list(se_sess); + spin_unlock_irqrestore(&vha->hw->hardware_lock, flags); + + return 1; +} + +static void tcm_qla2xxx_close_session(struct se_session *se_sess) +{ + struct qla_tgt_sess *sess = se_sess->fabric_sess_ptr; + struct scsi_qla_host *vha; + unsigned long flags; + + BUG_ON(!sess); + vha = sess->vha; + + spin_lock_irqsave(&vha->hw->hardware_lock, flags); + qlt_unreg_sess(sess); + spin_unlock_irqrestore(&vha->hw->hardware_lock, flags); +} + +static u32 tcm_qla2xxx_sess_get_index(struct se_session *se_sess) +{ + return 0; +} + +/* + * The LIO target core uses DMA_TO_DEVICE to mean that data is going + * to the target (eg handling a WRITE) and DMA_FROM_DEVICE to mean + * that data is coming from the target (eg handling a READ). However, + * this is just the opposite of what we have to tell the DMA mapping + * layer -- eg when handling a READ, the HBA will have to DMA the data + * out of memory so it can send it to the initiator, which means we + * need to use DMA_TO_DEVICE when we map the data. + */ +static enum dma_data_direction tcm_qla2xxx_mapping_dir(struct se_cmd *se_cmd) +{ + if (se_cmd->se_cmd_flags & SCF_BIDI) + return DMA_BIDIRECTIONAL; + + switch (se_cmd->data_direction) { + case DMA_TO_DEVICE: + return DMA_FROM_DEVICE; + case DMA_FROM_DEVICE: + return DMA_TO_DEVICE; + case DMA_NONE: + default: + return DMA_NONE; + } +} + +static int tcm_qla2xxx_write_pending(struct se_cmd *se_cmd) +{ + struct qla_tgt_cmd *cmd = container_of(se_cmd, + struct qla_tgt_cmd, se_cmd); + + cmd->bufflen = se_cmd->data_length; + cmd->dma_data_direction = tcm_qla2xxx_mapping_dir(se_cmd); + + cmd->sg_cnt = se_cmd->t_data_nents; + cmd->sg = se_cmd->t_data_sg; + + /* + * qla_target.c:qlt_rdy_to_xfer() will call pci_map_sg() to setup + * the SGL mappings into PCIe memory for incoming FCP WRITE data. + */ + return qlt_rdy_to_xfer(cmd); +} + +static int tcm_qla2xxx_write_pending_status(struct se_cmd *se_cmd) +{ + unsigned long flags; + /* + * Check for WRITE_PENDING status to determine if we need to wait for + * CTIO aborts to be posted via hardware in tcm_qla2xxx_handle_data(). + */ + spin_lock_irqsave(&se_cmd->t_state_lock, flags); + if (se_cmd->t_state == TRANSPORT_WRITE_PENDING || + se_cmd->t_state == TRANSPORT_COMPLETE_QF_WP) { + spin_unlock_irqrestore(&se_cmd->t_state_lock, flags); + wait_for_completion_timeout(&se_cmd->t_transport_stop_comp, + 3000); + return 0; + } + spin_unlock_irqrestore(&se_cmd->t_state_lock, flags); + + return 0; +} + +static void tcm_qla2xxx_set_default_node_attrs(struct se_node_acl *nacl) +{ + return; +} + +static u32 tcm_qla2xxx_get_task_tag(struct se_cmd *se_cmd) +{ + struct qla_tgt_cmd *cmd = container_of(se_cmd, + struct qla_tgt_cmd, se_cmd); + + return cmd->tag; +} + +static int tcm_qla2xxx_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +/* + * Called from process context in qla_target.c:qlt_do_work() code + */ +static int tcm_qla2xxx_handle_cmd(scsi_qla_host_t *vha, struct qla_tgt_cmd *cmd, + unsigned char *cdb, uint32_t data_length, int fcp_task_attr, + int data_dir, int bidi) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct se_session *se_sess; + struct qla_tgt_sess *sess; + int flags = TARGET_SCF_ACK_KREF; + + if (bidi) + flags |= TARGET_SCF_BIDI_OP; + + sess = cmd->sess; + if (!sess) { + pr_err("Unable to locate struct qla_tgt_sess from qla_tgt_cmd\n"); + return -EINVAL; + } + + se_sess = sess->se_sess; + if (!se_sess) { + pr_err("Unable to locate active struct se_session\n"); + return -EINVAL; + } + + target_submit_cmd(se_cmd, se_sess, cdb, &cmd->sense_buffer[0], + cmd->unpacked_lun, data_length, fcp_task_attr, + data_dir, flags); + return 0; +} + +static void tcm_qla2xxx_do_rsp(struct work_struct *work) +{ + struct qla_tgt_cmd *cmd = container_of(work, struct qla_tgt_cmd, work); + /* + * Dispatch ->queue_status from workqueue process context + */ + transport_generic_request_failure(&cmd->se_cmd); +} + +/* + * Called from qla_target.c:qlt_do_ctio_completion() + */ +static int tcm_qla2xxx_handle_data(struct qla_tgt_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + unsigned long flags; + /* + * Ensure that the complete FCP WRITE payload has been received. + * Otherwise return an exception via CHECK_CONDITION status. + */ + if (!cmd->write_data_transferred) { + /* + * Check if se_cmd has already been aborted via LUN_RESET, and + * waiting upon completion in tcm_qla2xxx_write_pending_status() + */ + spin_lock_irqsave(&se_cmd->t_state_lock, flags); + if (se_cmd->transport_state & CMD_T_ABORTED) { + spin_unlock_irqrestore(&se_cmd->t_state_lock, flags); + complete(&se_cmd->t_transport_stop_comp); + return 0; + } + spin_unlock_irqrestore(&se_cmd->t_state_lock, flags); + + se_cmd->scsi_sense_reason = TCM_CHECK_CONDITION_ABORT_CMD; + INIT_WORK(&cmd->work, tcm_qla2xxx_do_rsp); + queue_work(tcm_qla2xxx_free_wq, &cmd->work); + return 0; + } + /* + * We now tell TCM to queue this WRITE CDB with TRANSPORT_PROCESS_WRITE + * status to the backstore processing thread. + */ + return transport_generic_handle_data(&cmd->se_cmd); +} + +/* + * Called from qla_target.c:qlt_issue_task_mgmt() + */ +int tcm_qla2xxx_handle_tmr(struct qla_tgt_mgmt_cmd *mcmd, uint32_t lun, + uint8_t tmr_func, uint32_t tag) +{ + struct qla_tgt_sess *sess = mcmd->sess; + struct se_cmd *se_cmd = &mcmd->se_cmd; + + return target_submit_tmr(se_cmd, sess->se_sess, NULL, lun, mcmd, + tmr_func, GFP_ATOMIC, tag, TARGET_SCF_ACK_KREF); +} + +static int tcm_qla2xxx_queue_data_in(struct se_cmd *se_cmd) +{ + struct qla_tgt_cmd *cmd = container_of(se_cmd, + struct qla_tgt_cmd, se_cmd); + + cmd->bufflen = se_cmd->data_length; + cmd->dma_data_direction = tcm_qla2xxx_mapping_dir(se_cmd); + cmd->aborted = (se_cmd->transport_state & CMD_T_ABORTED); + + cmd->sg_cnt = se_cmd->t_data_nents; + cmd->sg = se_cmd->t_data_sg; + cmd->offset = 0; + + /* + * Now queue completed DATA_IN the qla2xxx LLD and response ring + */ + return qlt_xmit_response(cmd, QLA_TGT_XMIT_DATA|QLA_TGT_XMIT_STATUS, + se_cmd->scsi_status); +} + +static int tcm_qla2xxx_queue_status(struct se_cmd *se_cmd) +{ + struct qla_tgt_cmd *cmd = container_of(se_cmd, + struct qla_tgt_cmd, se_cmd); + int xmit_type = QLA_TGT_XMIT_STATUS; + + cmd->bufflen = se_cmd->data_length; + cmd->sg = NULL; + cmd->sg_cnt = 0; + cmd->offset = 0; + cmd->dma_data_direction = tcm_qla2xxx_mapping_dir(se_cmd); + cmd->aborted = (se_cmd->transport_state & CMD_T_ABORTED); + + if (se_cmd->data_direction == DMA_FROM_DEVICE) { + /* + * For FCP_READ with CHECK_CONDITION status, clear cmd->bufflen + * for qla_tgt_xmit_response LLD code + */ + se_cmd->se_cmd_flags |= SCF_UNDERFLOW_BIT; + se_cmd->residual_count = se_cmd->data_length; + + cmd->bufflen = 0; + } + /* + * Now queue status response to qla2xxx LLD code and response ring + */ + return qlt_xmit_response(cmd, xmit_type, se_cmd->scsi_status); +} + +static int tcm_qla2xxx_queue_tm_rsp(struct se_cmd *se_cmd) +{ + struct se_tmr_req *se_tmr = se_cmd->se_tmr_req; + struct qla_tgt_mgmt_cmd *mcmd = container_of(se_cmd, + struct qla_tgt_mgmt_cmd, se_cmd); + + pr_debug("queue_tm_rsp: mcmd: %p func: 0x%02x response: 0x%02x\n", + mcmd, se_tmr->function, se_tmr->response); + /* + * Do translation between TCM TM response codes and + * QLA2xxx FC TM response codes. + */ + switch (se_tmr->response) { + case TMR_FUNCTION_COMPLETE: + mcmd->fc_tm_rsp = FC_TM_SUCCESS; + break; + case TMR_TASK_DOES_NOT_EXIST: + mcmd->fc_tm_rsp = FC_TM_BAD_CMD; + break; + case TMR_FUNCTION_REJECTED: + mcmd->fc_tm_rsp = FC_TM_REJECT; + break; + case TMR_LUN_DOES_NOT_EXIST: + default: + mcmd->fc_tm_rsp = FC_TM_FAILED; + break; + } + /* + * Queue the TM response to QLA2xxx LLD to build a + * CTIO response packet. + */ + qlt_xmit_tm_rsp(mcmd); + + return 0; +} + +static u16 tcm_qla2xxx_get_fabric_sense_len(void) +{ + return 0; +} + +static u16 tcm_qla2xxx_set_fabric_sense_len(struct se_cmd *se_cmd, + u32 sense_length) +{ + return 0; +} + +/* Local pointer to allocated TCM configfs fabric module */ +struct target_fabric_configfs *tcm_qla2xxx_fabric_configfs; +struct target_fabric_configfs *tcm_qla2xxx_npiv_fabric_configfs; + +static int tcm_qla2xxx_setup_nacl_from_rport( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct tcm_qla2xxx_lport *lport, + struct tcm_qla2xxx_nacl *nacl, + u64 rport_wwnn) +{ + struct scsi_qla_host *vha = lport->qla_vha; + struct Scsi_Host *sh = vha->host; + struct fc_host_attrs *fc_host = shost_to_fc_host(sh); + struct fc_rport *rport; + unsigned long flags; + void *node; + int rc; + + /* + * Scan the existing rports, and create a session for the + * explict NodeACL is an matching rport->node_name already + * exists. + */ + spin_lock_irqsave(sh->host_lock, flags); + list_for_each_entry(rport, &fc_host->rports, peers) { + if (rport_wwnn != rport->node_name) + continue; + + pr_debug("Located existing rport_wwpn and rport->node_name: 0x%016LX, port_id: 0x%04x\n", + rport->node_name, rport->port_id); + nacl->nport_id = rport->port_id; + + spin_unlock_irqrestore(sh->host_lock, flags); + + spin_lock_irqsave(&vha->hw->hardware_lock, flags); + node = btree_lookup32(&lport->lport_fcport_map, rport->port_id); + if (node) { + rc = btree_update32(&lport->lport_fcport_map, + rport->port_id, se_nacl); + } else { + rc = btree_insert32(&lport->lport_fcport_map, + rport->port_id, se_nacl, + GFP_ATOMIC); + } + spin_unlock_irqrestore(&vha->hw->hardware_lock, flags); + + if (rc) { + pr_err("Unable to insert se_nacl into fcport_map"); + WARN_ON(rc > 0); + return rc; + } + + pr_debug("Inserted into fcport_map: %p for WWNN: 0x%016LX, port_id: 0x%08x\n", + se_nacl, rport_wwnn, nacl->nport_id); + + return 1; + } + spin_unlock_irqrestore(sh->host_lock, flags); + + return 0; +} + +/* + * Expected to be called with struct qla_hw_data->hardware_lock held + */ +static void tcm_qla2xxx_clear_nacl_from_fcport_map(struct qla_tgt_sess *sess) +{ + struct se_node_acl *se_nacl = sess->se_sess->se_node_acl; + struct se_portal_group *se_tpg = se_nacl->se_tpg; + struct se_wwn *se_wwn = se_tpg->se_tpg_wwn; + struct tcm_qla2xxx_lport *lport = container_of(se_wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct tcm_qla2xxx_nacl *nacl = container_of(se_nacl, + struct tcm_qla2xxx_nacl, se_node_acl); + void *node; + + pr_debug("fc_rport domain: port_id 0x%06x\n", nacl->nport_id); + + node = btree_remove32(&lport->lport_fcport_map, nacl->nport_id); + WARN_ON(node && (node != se_nacl)); + + pr_debug("Removed from fcport_map: %p for WWNN: 0x%016LX, port_id: 0x%06x\n", + se_nacl, nacl->nport_wwnn, nacl->nport_id); +} + +static void tcm_qla2xxx_put_sess(struct qla_tgt_sess *sess) +{ + target_put_session(sess->se_sess); +} + +static void tcm_qla2xxx_shutdown_sess(struct qla_tgt_sess *sess) +{ + tcm_qla2xxx_shutdown_session(sess->se_sess); +} + +static struct se_node_acl *tcm_qla2xxx_make_nodeacl( + struct se_portal_group *se_tpg, + struct config_group *group, + const char *name) +{ + struct se_wwn *se_wwn = se_tpg->se_tpg_wwn; + struct tcm_qla2xxx_lport *lport = container_of(se_wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct se_node_acl *se_nacl, *se_nacl_new; + struct tcm_qla2xxx_nacl *nacl; + u64 wwnn; + u32 qla2xxx_nexus_depth; + int rc; + + if (tcm_qla2xxx_parse_wwn(name, &wwnn, 1) < 0) + return ERR_PTR(-EINVAL); + + se_nacl_new = tcm_qla2xxx_alloc_fabric_acl(se_tpg); + if (!se_nacl_new) + return ERR_PTR(-ENOMEM); +/* #warning FIXME: Hardcoded qla2xxx_nexus depth in tcm_qla2xxx_make_nodeacl */ + qla2xxx_nexus_depth = 1; + + /* + * se_nacl_new may be released by core_tpg_add_initiator_node_acl() + * when converting a NodeACL from demo mode -> explict + */ + se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new, + name, qla2xxx_nexus_depth); + if (IS_ERR(se_nacl)) { + tcm_qla2xxx_release_fabric_acl(se_tpg, se_nacl_new); + return se_nacl; + } + /* + * Locate our struct tcm_qla2xxx_nacl and set the FC Nport WWPN + */ + nacl = container_of(se_nacl, struct tcm_qla2xxx_nacl, se_node_acl); + nacl->nport_wwnn = wwnn; + tcm_qla2xxx_format_wwn(&nacl->nport_name[0], TCM_QLA2XXX_NAMELEN, wwnn); + /* + * Setup a se_nacl handle based on an a matching struct fc_rport setup + * via drivers/scsi/qla2xxx/qla_init.c:qla2x00_reg_remote_port() + */ + rc = tcm_qla2xxx_setup_nacl_from_rport(se_tpg, se_nacl, lport, + nacl, wwnn); + if (rc < 0) { + tcm_qla2xxx_release_fabric_acl(se_tpg, se_nacl_new); + return ERR_PTR(rc); + } + + return se_nacl; +} + +static void tcm_qla2xxx_drop_nodeacl(struct se_node_acl *se_acl) +{ + struct se_portal_group *se_tpg = se_acl->se_tpg; + struct tcm_qla2xxx_nacl *nacl = container_of(se_acl, + struct tcm_qla2xxx_nacl, se_node_acl); + + core_tpg_del_initiator_node_acl(se_tpg, se_acl, 1); + kfree(nacl); +} + +/* Start items for tcm_qla2xxx_tpg_attrib_cit */ + +#define DEF_QLA_TPG_ATTRIB(name) \ + \ +static ssize_t tcm_qla2xxx_tpg_attrib_show_##name( \ + struct se_portal_group *se_tpg, \ + char *page) \ +{ \ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, \ + struct tcm_qla2xxx_tpg, se_tpg); \ + \ + return sprintf(page, "%u\n", QLA_TPG_ATTRIB(tpg)->name); \ +} \ + \ +static ssize_t tcm_qla2xxx_tpg_attrib_store_##name( \ + struct se_portal_group *se_tpg, \ + const char *page, \ + size_t count) \ +{ \ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, \ + struct tcm_qla2xxx_tpg, se_tpg); \ + unsigned long val; \ + int ret; \ + \ + ret = kstrtoul(page, 0, &val); \ + if (ret < 0) { \ + pr_err("kstrtoul() failed with" \ + " ret: %d\n", ret); \ + return -EINVAL; \ + } \ + ret = tcm_qla2xxx_set_attrib_##name(tpg, val); \ + \ + return (!ret) ? count : -EINVAL; \ +} + +#define DEF_QLA_TPG_ATTR_BOOL(_name) \ + \ +static int tcm_qla2xxx_set_attrib_##_name( \ + struct tcm_qla2xxx_tpg *tpg, \ + unsigned long val) \ +{ \ + struct tcm_qla2xxx_tpg_attrib *a = &tpg->tpg_attrib; \ + \ + if ((val != 0) && (val != 1)) { \ + pr_err("Illegal boolean value %lu\n", val); \ + return -EINVAL; \ + } \ + \ + a->_name = val; \ + return 0; \ +} + +#define QLA_TPG_ATTR(_name, _mode) \ + TF_TPG_ATTRIB_ATTR(tcm_qla2xxx, _name, _mode); + +/* + * Define tcm_qla2xxx_tpg_attrib_s_generate_node_acls + */ +DEF_QLA_TPG_ATTR_BOOL(generate_node_acls); +DEF_QLA_TPG_ATTRIB(generate_node_acls); +QLA_TPG_ATTR(generate_node_acls, S_IRUGO | S_IWUSR); + +/* + Define tcm_qla2xxx_attrib_s_cache_dynamic_acls + */ +DEF_QLA_TPG_ATTR_BOOL(cache_dynamic_acls); +DEF_QLA_TPG_ATTRIB(cache_dynamic_acls); +QLA_TPG_ATTR(cache_dynamic_acls, S_IRUGO | S_IWUSR); + +/* + * Define tcm_qla2xxx_tpg_attrib_s_demo_mode_write_protect + */ +DEF_QLA_TPG_ATTR_BOOL(demo_mode_write_protect); +DEF_QLA_TPG_ATTRIB(demo_mode_write_protect); +QLA_TPG_ATTR(demo_mode_write_protect, S_IRUGO | S_IWUSR); + +/* + * Define tcm_qla2xxx_tpg_attrib_s_prod_mode_write_protect + */ +DEF_QLA_TPG_ATTR_BOOL(prod_mode_write_protect); +DEF_QLA_TPG_ATTRIB(prod_mode_write_protect); +QLA_TPG_ATTR(prod_mode_write_protect, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *tcm_qla2xxx_tpg_attrib_attrs[] = { + &tcm_qla2xxx_tpg_attrib_generate_node_acls.attr, + &tcm_qla2xxx_tpg_attrib_cache_dynamic_acls.attr, + &tcm_qla2xxx_tpg_attrib_demo_mode_write_protect.attr, + &tcm_qla2xxx_tpg_attrib_prod_mode_write_protect.attr, + NULL, +}; + +/* End items for tcm_qla2xxx_tpg_attrib_cit */ + +static ssize_t tcm_qla2xxx_tpg_show_enable( + struct se_portal_group *se_tpg, + char *page) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + + return snprintf(page, PAGE_SIZE, "%d\n", + atomic_read(&tpg->lport_tpg_enabled)); +} + +static ssize_t tcm_qla2xxx_tpg_store_enable( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct se_wwn *se_wwn = se_tpg->se_tpg_wwn; + struct tcm_qla2xxx_lport *lport = container_of(se_wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct scsi_qla_host *vha = lport->qla_vha; + struct qla_hw_data *ha = vha->hw; + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + unsigned long op; + int rc; + + rc = kstrtoul(page, 0, &op); + if (rc < 0) { + pr_err("kstrtoul() returned %d\n", rc); + return -EINVAL; + } + if ((op != 1) && (op != 0)) { + pr_err("Illegal value for tpg_enable: %lu\n", op); + return -EINVAL; + } + + if (op) { + atomic_set(&tpg->lport_tpg_enabled, 1); + qlt_enable_vha(vha); + } else { + if (!ha->tgt.qla_tgt) { + pr_err("truct qla_hw_data *ha->tgt.qla_tgt is NULL\n"); + return -ENODEV; + } + atomic_set(&tpg->lport_tpg_enabled, 0); + qlt_stop_phase1(ha->tgt.qla_tgt); + } + + return count; +} + +TF_TPG_BASE_ATTR(tcm_qla2xxx, enable, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *tcm_qla2xxx_tpg_attrs[] = { + &tcm_qla2xxx_tpg_enable.attr, + NULL, +}; + +static struct se_portal_group *tcm_qla2xxx_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct tcm_qla2xxx_lport *lport = container_of(wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct tcm_qla2xxx_tpg *tpg; + unsigned long tpgt; + int ret; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 10, &tpgt) || tpgt > USHRT_MAX) + return ERR_PTR(-EINVAL); + + if (!lport->qla_npiv_vp && (tpgt != 1)) { + pr_err("In non NPIV mode, a single TPG=1 is used for HW port mappings\n"); + return ERR_PTR(-ENOSYS); + } + + tpg = kzalloc(sizeof(struct tcm_qla2xxx_tpg), GFP_KERNEL); + if (!tpg) { + pr_err("Unable to allocate struct tcm_qla2xxx_tpg\n"); + return ERR_PTR(-ENOMEM); + } + tpg->lport = lport; + tpg->lport_tpgt = tpgt; + /* + * By default allow READ-ONLY TPG demo-mode access w/ cached dynamic + * NodeACLs + */ + QLA_TPG_ATTRIB(tpg)->generate_node_acls = 1; + QLA_TPG_ATTRIB(tpg)->demo_mode_write_protect = 1; + QLA_TPG_ATTRIB(tpg)->cache_dynamic_acls = 1; + + ret = core_tpg_register(&tcm_qla2xxx_fabric_configfs->tf_ops, wwn, + &tpg->se_tpg, tpg, TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) { + kfree(tpg); + return NULL; + } + /* + * Setup local TPG=1 pointer for non NPIV mode. + */ + if (lport->qla_npiv_vp == NULL) + lport->tpg_1 = tpg; + + return &tpg->se_tpg; +} + +static void tcm_qla2xxx_drop_tpg(struct se_portal_group *se_tpg) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + struct scsi_qla_host *vha = lport->qla_vha; + struct qla_hw_data *ha = vha->hw; + /* + * Call into qla2x_target.c LLD logic to shutdown the active + * FC Nexuses and disable target mode operation for this qla_hw_data + */ + if (ha->tgt.qla_tgt && !ha->tgt.qla_tgt->tgt_stop) + qlt_stop_phase1(ha->tgt.qla_tgt); + + core_tpg_deregister(se_tpg); + /* + * Clear local TPG=1 pointer for non NPIV mode. + */ + if (lport->qla_npiv_vp == NULL) + lport->tpg_1 = NULL; + + kfree(tpg); +} + +static struct se_portal_group *tcm_qla2xxx_npiv_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct tcm_qla2xxx_lport *lport = container_of(wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct tcm_qla2xxx_tpg *tpg; + unsigned long tpgt; + int ret; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 10, &tpgt) || tpgt > USHRT_MAX) + return ERR_PTR(-EINVAL); + + tpg = kzalloc(sizeof(struct tcm_qla2xxx_tpg), GFP_KERNEL); + if (!tpg) { + pr_err("Unable to allocate struct tcm_qla2xxx_tpg\n"); + return ERR_PTR(-ENOMEM); + } + tpg->lport = lport; + tpg->lport_tpgt = tpgt; + + ret = core_tpg_register(&tcm_qla2xxx_npiv_fabric_configfs->tf_ops, wwn, + &tpg->se_tpg, tpg, TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) { + kfree(tpg); + return NULL; + } + return &tpg->se_tpg; +} + +/* + * Expected to be called with struct qla_hw_data->hardware_lock held + */ +static struct qla_tgt_sess *tcm_qla2xxx_find_sess_by_s_id( + scsi_qla_host_t *vha, + const uint8_t *s_id) +{ + struct qla_hw_data *ha = vha->hw; + struct tcm_qla2xxx_lport *lport; + struct se_node_acl *se_nacl; + struct tcm_qla2xxx_nacl *nacl; + u32 key; + + lport = ha->tgt.target_lport_ptr; + if (!lport) { + pr_err("Unable to locate struct tcm_qla2xxx_lport\n"); + dump_stack(); + return NULL; + } + + key = (((unsigned long)s_id[0] << 16) | + ((unsigned long)s_id[1] << 8) | + (unsigned long)s_id[2]); + pr_debug("find_sess_by_s_id: 0x%06x\n", key); + + se_nacl = btree_lookup32(&lport->lport_fcport_map, key); + if (!se_nacl) { + pr_debug("Unable to locate s_id: 0x%06x\n", key); + return NULL; + } + pr_debug("find_sess_by_s_id: located se_nacl: %p, initiatorname: %s\n", + se_nacl, se_nacl->initiatorname); + + nacl = container_of(se_nacl, struct tcm_qla2xxx_nacl, se_node_acl); + if (!nacl->qla_tgt_sess) { + pr_err("Unable to locate struct qla_tgt_sess\n"); + return NULL; + } + + return nacl->qla_tgt_sess; +} + +/* + * Expected to be called with struct qla_hw_data->hardware_lock held + */ +static void tcm_qla2xxx_set_sess_by_s_id( + struct tcm_qla2xxx_lport *lport, + struct se_node_acl *new_se_nacl, + struct tcm_qla2xxx_nacl *nacl, + struct se_session *se_sess, + struct qla_tgt_sess *qla_tgt_sess, + uint8_t *s_id) +{ + u32 key; + void *slot; + int rc; + + key = (((unsigned long)s_id[0] << 16) | + ((unsigned long)s_id[1] << 8) | + (unsigned long)s_id[2]); + pr_debug("set_sess_by_s_id: %06x\n", key); + + slot = btree_lookup32(&lport->lport_fcport_map, key); + if (!slot) { + if (new_se_nacl) { + pr_debug("Setting up new fc_port entry to new_se_nacl\n"); + nacl->nport_id = key; + rc = btree_insert32(&lport->lport_fcport_map, key, + new_se_nacl, GFP_ATOMIC); + if (rc) + printk(KERN_ERR "Unable to insert s_id into fcport_map: %06x\n", + (int)key); + } else { + pr_debug("Wiping nonexisting fc_port entry\n"); + } + + qla_tgt_sess->se_sess = se_sess; + nacl->qla_tgt_sess = qla_tgt_sess; + return; + } + + if (nacl->qla_tgt_sess) { + if (new_se_nacl == NULL) { + pr_debug("Clearing existing nacl->qla_tgt_sess and fc_port entry\n"); + btree_remove32(&lport->lport_fcport_map, key); + nacl->qla_tgt_sess = NULL; + return; + } + pr_debug("Replacing existing nacl->qla_tgt_sess and fc_port entry\n"); + btree_update32(&lport->lport_fcport_map, key, new_se_nacl); + qla_tgt_sess->se_sess = se_sess; + nacl->qla_tgt_sess = qla_tgt_sess; + return; + } + + if (new_se_nacl == NULL) { + pr_debug("Clearing existing fc_port entry\n"); + btree_remove32(&lport->lport_fcport_map, key); + return; + } + + pr_debug("Replacing existing fc_port entry w/o active nacl->qla_tgt_sess\n"); + btree_update32(&lport->lport_fcport_map, key, new_se_nacl); + qla_tgt_sess->se_sess = se_sess; + nacl->qla_tgt_sess = qla_tgt_sess; + + pr_debug("Setup nacl->qla_tgt_sess %p by s_id for se_nacl: %p, initiatorname: %s\n", + nacl->qla_tgt_sess, new_se_nacl, new_se_nacl->initiatorname); +} + +/* + * Expected to be called with struct qla_hw_data->hardware_lock held + */ +static struct qla_tgt_sess *tcm_qla2xxx_find_sess_by_loop_id( + scsi_qla_host_t *vha, + const uint16_t loop_id) +{ + struct qla_hw_data *ha = vha->hw; + struct tcm_qla2xxx_lport *lport; + struct se_node_acl *se_nacl; + struct tcm_qla2xxx_nacl *nacl; + struct tcm_qla2xxx_fc_loopid *fc_loopid; + + lport = ha->tgt.target_lport_ptr; + if (!lport) { + pr_err("Unable to locate struct tcm_qla2xxx_lport\n"); + dump_stack(); + return NULL; + } + + pr_debug("find_sess_by_loop_id: Using loop_id: 0x%04x\n", loop_id); + + fc_loopid = lport->lport_loopid_map + loop_id; + se_nacl = fc_loopid->se_nacl; + if (!se_nacl) { + pr_debug("Unable to locate se_nacl by loop_id: 0x%04x\n", + loop_id); + return NULL; + } + + nacl = container_of(se_nacl, struct tcm_qla2xxx_nacl, se_node_acl); + + if (!nacl->qla_tgt_sess) { + pr_err("Unable to locate struct qla_tgt_sess\n"); + return NULL; + } + + return nacl->qla_tgt_sess; +} + +/* + * Expected to be called with struct qla_hw_data->hardware_lock held + */ +static void tcm_qla2xxx_set_sess_by_loop_id( + struct tcm_qla2xxx_lport *lport, + struct se_node_acl *new_se_nacl, + struct tcm_qla2xxx_nacl *nacl, + struct se_session *se_sess, + struct qla_tgt_sess *qla_tgt_sess, + uint16_t loop_id) +{ + struct se_node_acl *saved_nacl; + struct tcm_qla2xxx_fc_loopid *fc_loopid; + + pr_debug("set_sess_by_loop_id: Using loop_id: 0x%04x\n", loop_id); + + fc_loopid = &((struct tcm_qla2xxx_fc_loopid *) + lport->lport_loopid_map)[loop_id]; + + saved_nacl = fc_loopid->se_nacl; + if (!saved_nacl) { + pr_debug("Setting up new fc_loopid->se_nacl to new_se_nacl\n"); + fc_loopid->se_nacl = new_se_nacl; + if (qla_tgt_sess->se_sess != se_sess) + qla_tgt_sess->se_sess = se_sess; + if (nacl->qla_tgt_sess != qla_tgt_sess) + nacl->qla_tgt_sess = qla_tgt_sess; + return; + } + + if (nacl->qla_tgt_sess) { + if (new_se_nacl == NULL) { + pr_debug("Clearing nacl->qla_tgt_sess and fc_loopid->se_nacl\n"); + fc_loopid->se_nacl = NULL; + nacl->qla_tgt_sess = NULL; + return; + } + + pr_debug("Replacing existing nacl->qla_tgt_sess and fc_loopid->se_nacl\n"); + fc_loopid->se_nacl = new_se_nacl; + if (qla_tgt_sess->se_sess != se_sess) + qla_tgt_sess->se_sess = se_sess; + if (nacl->qla_tgt_sess != qla_tgt_sess) + nacl->qla_tgt_sess = qla_tgt_sess; + return; + } + + if (new_se_nacl == NULL) { + pr_debug("Clearing fc_loopid->se_nacl\n"); + fc_loopid->se_nacl = NULL; + return; + } + + pr_debug("Replacing existing fc_loopid->se_nacl w/o active nacl->qla_tgt_sess\n"); + fc_loopid->se_nacl = new_se_nacl; + if (qla_tgt_sess->se_sess != se_sess) + qla_tgt_sess->se_sess = se_sess; + if (nacl->qla_tgt_sess != qla_tgt_sess) + nacl->qla_tgt_sess = qla_tgt_sess; + + pr_debug("Setup nacl->qla_tgt_sess %p by loop_id for se_nacl: %p, initiatorname: %s\n", + nacl->qla_tgt_sess, new_se_nacl, new_se_nacl->initiatorname); +} + +static void tcm_qla2xxx_free_session(struct qla_tgt_sess *sess) +{ + struct qla_tgt *tgt = sess->tgt; + struct qla_hw_data *ha = tgt->ha; + struct se_session *se_sess; + struct se_node_acl *se_nacl; + struct tcm_qla2xxx_lport *lport; + struct tcm_qla2xxx_nacl *nacl; + unsigned char be_sid[3]; + unsigned long flags; + + BUG_ON(in_interrupt()); + + se_sess = sess->se_sess; + if (!se_sess) { + pr_err("struct qla_tgt_sess->se_sess is NULL\n"); + dump_stack(); + return; + } + se_nacl = se_sess->se_node_acl; + nacl = container_of(se_nacl, struct tcm_qla2xxx_nacl, se_node_acl); + + lport = ha->tgt.target_lport_ptr; + if (!lport) { + pr_err("Unable to locate struct tcm_qla2xxx_lport\n"); + dump_stack(); + return; + } + target_wait_for_sess_cmds(se_sess, 0); + /* + * And now clear the se_nacl and session pointers from our HW lport + * mappings for fabric S_ID and LOOP_ID. + */ + memset(&be_sid, 0, 3); + be_sid[0] = sess->s_id.b.domain; + be_sid[1] = sess->s_id.b.area; + be_sid[2] = sess->s_id.b.al_pa; + + spin_lock_irqsave(&ha->hardware_lock, flags); + tcm_qla2xxx_set_sess_by_s_id(lport, NULL, nacl, se_sess, + sess, be_sid); + tcm_qla2xxx_set_sess_by_loop_id(lport, NULL, nacl, se_sess, + sess, sess->loop_id); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + transport_deregister_session_configfs(sess->se_sess); + transport_deregister_session(sess->se_sess); +} + +/* + * Called via qlt_create_sess():ha->qla2x_tmpl->check_initiator_node_acl() + * to locate struct se_node_acl + */ +static int tcm_qla2xxx_check_initiator_node_acl( + scsi_qla_host_t *vha, + unsigned char *fc_wwpn, + void *qla_tgt_sess, + uint8_t *s_id, + uint16_t loop_id) +{ + struct qla_hw_data *ha = vha->hw; + struct tcm_qla2xxx_lport *lport; + struct tcm_qla2xxx_tpg *tpg; + struct tcm_qla2xxx_nacl *nacl; + struct se_portal_group *se_tpg; + struct se_node_acl *se_nacl; + struct se_session *se_sess; + struct qla_tgt_sess *sess = qla_tgt_sess; + unsigned char port_name[36]; + unsigned long flags; + + lport = ha->tgt.target_lport_ptr; + if (!lport) { + pr_err("Unable to locate struct tcm_qla2xxx_lport\n"); + dump_stack(); + return -EINVAL; + } + /* + * Locate the TPG=1 reference.. + */ + tpg = lport->tpg_1; + if (!tpg) { + pr_err("Unable to lcoate struct tcm_qla2xxx_lport->tpg_1\n"); + return -EINVAL; + } + se_tpg = &tpg->se_tpg; + + se_sess = transport_init_session(); + if (IS_ERR(se_sess)) { + pr_err("Unable to initialize struct se_session\n"); + return PTR_ERR(se_sess); + } + /* + * Format the FCP Initiator port_name into colon seperated values to + * match the format by tcm_qla2xxx explict ConfigFS NodeACLs. + */ + memset(&port_name, 0, 36); + snprintf(port_name, 36, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + fc_wwpn[0], fc_wwpn[1], fc_wwpn[2], fc_wwpn[3], fc_wwpn[4], + fc_wwpn[5], fc_wwpn[6], fc_wwpn[7]); + /* + * Locate our struct se_node_acl either from an explict NodeACL created + * via ConfigFS, or via running in TPG demo mode. + */ + se_sess->se_node_acl = core_tpg_check_initiator_node_acl(se_tpg, + port_name); + if (!se_sess->se_node_acl) { + transport_free_session(se_sess); + return -EINVAL; + } + se_nacl = se_sess->se_node_acl; + nacl = container_of(se_nacl, struct tcm_qla2xxx_nacl, se_node_acl); + /* + * And now setup the new se_nacl and session pointers into our HW lport + * mappings for fabric S_ID and LOOP_ID. + */ + spin_lock_irqsave(&ha->hardware_lock, flags); + tcm_qla2xxx_set_sess_by_s_id(lport, se_nacl, nacl, se_sess, + qla_tgt_sess, s_id); + tcm_qla2xxx_set_sess_by_loop_id(lport, se_nacl, nacl, se_sess, + qla_tgt_sess, loop_id); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + /* + * Finally register the new FC Nexus with TCM + */ + __transport_register_session(se_nacl->se_tpg, se_nacl, se_sess, sess); + + return 0; +} + +/* + * Calls into tcm_qla2xxx used by qla2xxx LLD I/O path. + */ +static struct qla_tgt_func_tmpl tcm_qla2xxx_template = { + .handle_cmd = tcm_qla2xxx_handle_cmd, + .handle_data = tcm_qla2xxx_handle_data, + .handle_tmr = tcm_qla2xxx_handle_tmr, + .free_cmd = tcm_qla2xxx_free_cmd, + .free_mcmd = tcm_qla2xxx_free_mcmd, + .free_session = tcm_qla2xxx_free_session, + .check_initiator_node_acl = tcm_qla2xxx_check_initiator_node_acl, + .find_sess_by_s_id = tcm_qla2xxx_find_sess_by_s_id, + .find_sess_by_loop_id = tcm_qla2xxx_find_sess_by_loop_id, + .clear_nacl_from_fcport_map = tcm_qla2xxx_clear_nacl_from_fcport_map, + .put_sess = tcm_qla2xxx_put_sess, + .shutdown_sess = tcm_qla2xxx_shutdown_sess, +}; + +static int tcm_qla2xxx_init_lport(struct tcm_qla2xxx_lport *lport) +{ + int rc; + + rc = btree_init32(&lport->lport_fcport_map); + if (rc) { + pr_err("Unable to initialize lport->lport_fcport_map btree\n"); + return rc; + } + + lport->lport_loopid_map = vmalloc(sizeof(struct tcm_qla2xxx_fc_loopid) * + 65536); + if (!lport->lport_loopid_map) { + pr_err("Unable to allocate lport->lport_loopid_map of %zu bytes\n", + sizeof(struct tcm_qla2xxx_fc_loopid) * 65536); + btree_destroy32(&lport->lport_fcport_map); + return -ENOMEM; + } + memset(lport->lport_loopid_map, 0, sizeof(struct tcm_qla2xxx_fc_loopid) + * 65536); + pr_debug("qla2xxx: Allocated lport_loopid_map of %zu bytes\n", + sizeof(struct tcm_qla2xxx_fc_loopid) * 65536); + return 0; +} + +static int tcm_qla2xxx_lport_register_cb(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct tcm_qla2xxx_lport *lport; + /* + * Setup local pointer to vha, NPIV VP pointer (if present) and + * vha->tcm_lport pointer + */ + lport = (struct tcm_qla2xxx_lport *)ha->tgt.target_lport_ptr; + lport->qla_vha = vha; + + return 0; +} + +static struct se_wwn *tcm_qla2xxx_make_lport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct tcm_qla2xxx_lport *lport; + u64 wwpn; + int ret = -ENODEV; + + if (tcm_qla2xxx_parse_wwn(name, &wwpn, 1) < 0) + return ERR_PTR(-EINVAL); + + lport = kzalloc(sizeof(struct tcm_qla2xxx_lport), GFP_KERNEL); + if (!lport) { + pr_err("Unable to allocate struct tcm_qla2xxx_lport\n"); + return ERR_PTR(-ENOMEM); + } + lport->lport_wwpn = wwpn; + tcm_qla2xxx_format_wwn(&lport->lport_name[0], TCM_QLA2XXX_NAMELEN, + wwpn); + + ret = tcm_qla2xxx_init_lport(lport); + if (ret != 0) + goto out; + + ret = qlt_lport_register(&tcm_qla2xxx_template, wwpn, + tcm_qla2xxx_lport_register_cb, lport); + if (ret != 0) + goto out_lport; + + return &lport->lport_wwn; +out_lport: + vfree(lport->lport_loopid_map); + btree_destroy32(&lport->lport_fcport_map); +out: + kfree(lport); + return ERR_PTR(ret); +} + +static void tcm_qla2xxx_drop_lport(struct se_wwn *wwn) +{ + struct tcm_qla2xxx_lport *lport = container_of(wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct scsi_qla_host *vha = lport->qla_vha; + struct qla_hw_data *ha = vha->hw; + struct se_node_acl *node; + u32 key = 0; + + /* + * Call into qla2x_target.c LLD logic to complete the + * shutdown of struct qla_tgt after the call to + * qlt_stop_phase1() from tcm_qla2xxx_drop_tpg() above.. + */ + if (ha->tgt.qla_tgt && !ha->tgt.qla_tgt->tgt_stopped) + qlt_stop_phase2(ha->tgt.qla_tgt); + + qlt_lport_deregister(vha); + + vfree(lport->lport_loopid_map); + btree_for_each_safe32(&lport->lport_fcport_map, key, node) + btree_remove32(&lport->lport_fcport_map, key); + btree_destroy32(&lport->lport_fcport_map); + kfree(lport); +} + +static struct se_wwn *tcm_qla2xxx_npiv_make_lport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct tcm_qla2xxx_lport *lport; + u64 npiv_wwpn, npiv_wwnn; + int ret; + + if (tcm_qla2xxx_npiv_parse_wwn(name, strlen(name)+1, + &npiv_wwpn, &npiv_wwnn) < 0) + return ERR_PTR(-EINVAL); + + lport = kzalloc(sizeof(struct tcm_qla2xxx_lport), GFP_KERNEL); + if (!lport) { + pr_err("Unable to allocate struct tcm_qla2xxx_lport for NPIV\n"); + return ERR_PTR(-ENOMEM); + } + lport->lport_npiv_wwpn = npiv_wwpn; + lport->lport_npiv_wwnn = npiv_wwnn; + tcm_qla2xxx_npiv_format_wwn(&lport->lport_npiv_name[0], + TCM_QLA2XXX_NAMELEN, npiv_wwpn, npiv_wwnn); + +/* FIXME: tcm_qla2xxx_npiv_make_lport */ + ret = -ENOSYS; + if (ret != 0) + goto out; + + return &lport->lport_wwn; +out: + kfree(lport); + return ERR_PTR(ret); +} + +static void tcm_qla2xxx_npiv_drop_lport(struct se_wwn *wwn) +{ + struct tcm_qla2xxx_lport *lport = container_of(wwn, + struct tcm_qla2xxx_lport, lport_wwn); + struct scsi_qla_host *vha = lport->qla_vha; + struct Scsi_Host *sh = vha->host; + /* + * Notify libfc that we want to release the lport->npiv_vport + */ + fc_vport_terminate(lport->npiv_vport); + + scsi_host_put(sh); + kfree(lport); +} + + +static ssize_t tcm_qla2xxx_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, + "TCM QLOGIC QLA2XXX NPIV capable fabric module %s on %s/%s on " + UTS_RELEASE"\n", TCM_QLA2XXX_VERSION, utsname()->sysname, + utsname()->machine); +} + +TF_WWN_ATTR_RO(tcm_qla2xxx, version); + +static struct configfs_attribute *tcm_qla2xxx_wwn_attrs[] = { + &tcm_qla2xxx_wwn_version.attr, + NULL, +}; + +static struct target_core_fabric_ops tcm_qla2xxx_ops = { + .get_fabric_name = tcm_qla2xxx_get_fabric_name, + .get_fabric_proto_ident = tcm_qla2xxx_get_fabric_proto_ident, + .tpg_get_wwn = tcm_qla2xxx_get_fabric_wwn, + .tpg_get_tag = tcm_qla2xxx_get_tag, + .tpg_get_default_depth = tcm_qla2xxx_get_default_depth, + .tpg_get_pr_transport_id = tcm_qla2xxx_get_pr_transport_id, + .tpg_get_pr_transport_id_len = tcm_qla2xxx_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = tcm_qla2xxx_parse_pr_out_transport_id, + .tpg_check_demo_mode = tcm_qla2xxx_check_demo_mode, + .tpg_check_demo_mode_cache = tcm_qla2xxx_check_demo_mode_cache, + .tpg_check_demo_mode_write_protect = + tcm_qla2xxx_check_demo_write_protect, + .tpg_check_prod_mode_write_protect = + tcm_qla2xxx_check_prod_write_protect, + .tpg_check_demo_mode_login_only = tcm_qla2xxx_check_true, + .tpg_alloc_fabric_acl = tcm_qla2xxx_alloc_fabric_acl, + .tpg_release_fabric_acl = tcm_qla2xxx_release_fabric_acl, + .tpg_get_inst_index = tcm_qla2xxx_tpg_get_inst_index, + .new_cmd_map = NULL, + .check_stop_free = tcm_qla2xxx_check_stop_free, + .release_cmd = tcm_qla2xxx_release_cmd, + .shutdown_session = tcm_qla2xxx_shutdown_session, + .close_session = tcm_qla2xxx_close_session, + .sess_get_index = tcm_qla2xxx_sess_get_index, + .sess_get_initiator_sid = NULL, + .write_pending = tcm_qla2xxx_write_pending, + .write_pending_status = tcm_qla2xxx_write_pending_status, + .set_default_node_attributes = tcm_qla2xxx_set_default_node_attrs, + .get_task_tag = tcm_qla2xxx_get_task_tag, + .get_cmd_state = tcm_qla2xxx_get_cmd_state, + .queue_data_in = tcm_qla2xxx_queue_data_in, + .queue_status = tcm_qla2xxx_queue_status, + .queue_tm_rsp = tcm_qla2xxx_queue_tm_rsp, + .get_fabric_sense_len = tcm_qla2xxx_get_fabric_sense_len, + .set_fabric_sense_len = tcm_qla2xxx_set_fabric_sense_len, + /* + * Setup function pointers for generic logic in + * target_core_fabric_configfs.c + */ + .fabric_make_wwn = tcm_qla2xxx_make_lport, + .fabric_drop_wwn = tcm_qla2xxx_drop_lport, + .fabric_make_tpg = tcm_qla2xxx_make_tpg, + .fabric_drop_tpg = tcm_qla2xxx_drop_tpg, + .fabric_post_link = NULL, + .fabric_pre_unlink = NULL, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = tcm_qla2xxx_make_nodeacl, + .fabric_drop_nodeacl = tcm_qla2xxx_drop_nodeacl, +}; + +static struct target_core_fabric_ops tcm_qla2xxx_npiv_ops = { + .get_fabric_name = tcm_qla2xxx_npiv_get_fabric_name, + .get_fabric_proto_ident = tcm_qla2xxx_get_fabric_proto_ident, + .tpg_get_wwn = tcm_qla2xxx_npiv_get_fabric_wwn, + .tpg_get_tag = tcm_qla2xxx_get_tag, + .tpg_get_default_depth = tcm_qla2xxx_get_default_depth, + .tpg_get_pr_transport_id = tcm_qla2xxx_get_pr_transport_id, + .tpg_get_pr_transport_id_len = tcm_qla2xxx_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = tcm_qla2xxx_parse_pr_out_transport_id, + .tpg_check_demo_mode = tcm_qla2xxx_check_false, + .tpg_check_demo_mode_cache = tcm_qla2xxx_check_true, + .tpg_check_demo_mode_write_protect = tcm_qla2xxx_check_true, + .tpg_check_prod_mode_write_protect = tcm_qla2xxx_check_false, + .tpg_check_demo_mode_login_only = tcm_qla2xxx_check_true, + .tpg_alloc_fabric_acl = tcm_qla2xxx_alloc_fabric_acl, + .tpg_release_fabric_acl = tcm_qla2xxx_release_fabric_acl, + .tpg_get_inst_index = tcm_qla2xxx_tpg_get_inst_index, + .release_cmd = tcm_qla2xxx_release_cmd, + .shutdown_session = tcm_qla2xxx_shutdown_session, + .close_session = tcm_qla2xxx_close_session, + .sess_get_index = tcm_qla2xxx_sess_get_index, + .sess_get_initiator_sid = NULL, + .write_pending = tcm_qla2xxx_write_pending, + .write_pending_status = tcm_qla2xxx_write_pending_status, + .set_default_node_attributes = tcm_qla2xxx_set_default_node_attrs, + .get_task_tag = tcm_qla2xxx_get_task_tag, + .get_cmd_state = tcm_qla2xxx_get_cmd_state, + .queue_data_in = tcm_qla2xxx_queue_data_in, + .queue_status = tcm_qla2xxx_queue_status, + .queue_tm_rsp = tcm_qla2xxx_queue_tm_rsp, + .get_fabric_sense_len = tcm_qla2xxx_get_fabric_sense_len, + .set_fabric_sense_len = tcm_qla2xxx_set_fabric_sense_len, + /* + * Setup function pointers for generic logic in + * target_core_fabric_configfs.c + */ + .fabric_make_wwn = tcm_qla2xxx_npiv_make_lport, + .fabric_drop_wwn = tcm_qla2xxx_npiv_drop_lport, + .fabric_make_tpg = tcm_qla2xxx_npiv_make_tpg, + .fabric_drop_tpg = tcm_qla2xxx_drop_tpg, + .fabric_post_link = NULL, + .fabric_pre_unlink = NULL, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = tcm_qla2xxx_make_nodeacl, + .fabric_drop_nodeacl = tcm_qla2xxx_drop_nodeacl, +}; + +static int tcm_qla2xxx_register_configfs(void) +{ + struct target_fabric_configfs *fabric, *npiv_fabric; + int ret; + + pr_debug("TCM QLOGIC QLA2XXX fabric module %s on %s/%s on " + UTS_RELEASE"\n", TCM_QLA2XXX_VERSION, utsname()->sysname, + utsname()->machine); + /* + * Register the top level struct config_item_type with TCM core + */ + fabric = target_fabric_configfs_init(THIS_MODULE, "qla2xxx"); + if (IS_ERR(fabric)) { + pr_err("target_fabric_configfs_init() failed\n"); + return PTR_ERR(fabric); + } + /* + * Setup fabric->tf_ops from our local tcm_qla2xxx_ops + */ + fabric->tf_ops = tcm_qla2xxx_ops; + /* + * Setup default attribute lists for various fabric->tf_cit_tmpl + */ + TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = tcm_qla2xxx_wwn_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = tcm_qla2xxx_tpg_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = + tcm_qla2xxx_tpg_attrib_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; + /* + * Register the fabric for use within TCM + */ + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + pr_err("target_fabric_configfs_register() failed for TCM_QLA2XXX\n"); + return ret; + } + /* + * Setup our local pointer to *fabric + */ + tcm_qla2xxx_fabric_configfs = fabric; + pr_debug("TCM_QLA2XXX[0] - Set fabric -> tcm_qla2xxx_fabric_configfs\n"); + + /* + * Register the top level struct config_item_type for NPIV with TCM core + */ + npiv_fabric = target_fabric_configfs_init(THIS_MODULE, "qla2xxx_npiv"); + if (IS_ERR(npiv_fabric)) { + pr_err("target_fabric_configfs_init() failed\n"); + ret = PTR_ERR(npiv_fabric); + goto out_fabric; + } + /* + * Setup fabric->tf_ops from our local tcm_qla2xxx_npiv_ops + */ + npiv_fabric->tf_ops = tcm_qla2xxx_npiv_ops; + /* + * Setup default attribute lists for various npiv_fabric->tf_cit_tmpl + */ + TF_CIT_TMPL(npiv_fabric)->tfc_wwn_cit.ct_attrs = tcm_qla2xxx_wwn_attrs; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_param_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + TF_CIT_TMPL(npiv_fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; + /* + * Register the npiv_fabric for use within TCM + */ + ret = target_fabric_configfs_register(npiv_fabric); + if (ret < 0) { + pr_err("target_fabric_configfs_register() failed for TCM_QLA2XXX\n"); + goto out_fabric; + } + /* + * Setup our local pointer to *npiv_fabric + */ + tcm_qla2xxx_npiv_fabric_configfs = npiv_fabric; + pr_debug("TCM_QLA2XXX[0] - Set fabric -> tcm_qla2xxx_npiv_fabric_configfs\n"); + + tcm_qla2xxx_free_wq = alloc_workqueue("tcm_qla2xxx_free", + WQ_MEM_RECLAIM, 0); + if (!tcm_qla2xxx_free_wq) { + ret = -ENOMEM; + goto out_fabric_npiv; + } + + tcm_qla2xxx_cmd_wq = alloc_workqueue("tcm_qla2xxx_cmd", 0, 0); + if (!tcm_qla2xxx_cmd_wq) { + ret = -ENOMEM; + goto out_free_wq; + } + + return 0; + +out_free_wq: + destroy_workqueue(tcm_qla2xxx_free_wq); +out_fabric_npiv: + target_fabric_configfs_deregister(tcm_qla2xxx_npiv_fabric_configfs); +out_fabric: + target_fabric_configfs_deregister(tcm_qla2xxx_fabric_configfs); + return ret; +} + +static void tcm_qla2xxx_deregister_configfs(void) +{ + destroy_workqueue(tcm_qla2xxx_cmd_wq); + destroy_workqueue(tcm_qla2xxx_free_wq); + + target_fabric_configfs_deregister(tcm_qla2xxx_fabric_configfs); + tcm_qla2xxx_fabric_configfs = NULL; + pr_debug("TCM_QLA2XXX[0] - Cleared tcm_qla2xxx_fabric_configfs\n"); + + target_fabric_configfs_deregister(tcm_qla2xxx_npiv_fabric_configfs); + tcm_qla2xxx_npiv_fabric_configfs = NULL; + pr_debug("TCM_QLA2XXX[0] - Cleared tcm_qla2xxx_npiv_fabric_configfs\n"); +} + +static int __init tcm_qla2xxx_init(void) +{ + int ret; + + ret = tcm_qla2xxx_register_configfs(); + if (ret < 0) + return ret; + + return 0; +} + +static void __exit tcm_qla2xxx_exit(void) +{ + tcm_qla2xxx_deregister_configfs(); +} + +MODULE_DESCRIPTION("TCM QLA2XXX series NPIV enabled fabric driver"); +MODULE_LICENSE("GPL"); +module_init(tcm_qla2xxx_init); +module_exit(tcm_qla2xxx_exit); diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.h b/drivers/scsi/qla2xxx/tcm_qla2xxx.h new file mode 100644 index 00000000000..82549810335 --- /dev/null +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.h @@ -0,0 +1,82 @@ +#include <target/target_core_base.h> +#include <linux/btree.h> + +#define TCM_QLA2XXX_VERSION "v0.1" +/* length of ASCII WWPNs including pad */ +#define TCM_QLA2XXX_NAMELEN 32 +/* lenth of ASCII NPIV 'WWPN+WWNN' including pad */ +#define TCM_QLA2XXX_NPIV_NAMELEN 66 + +#include "qla_target.h" + +struct tcm_qla2xxx_nacl { + /* From libfc struct fc_rport->port_id */ + u32 nport_id; + /* Binary World Wide unique Node Name for remote FC Initiator Nport */ + u64 nport_wwnn; + /* ASCII formatted WWPN for FC Initiator Nport */ + char nport_name[TCM_QLA2XXX_NAMELEN]; + /* Pointer to qla_tgt_sess */ + struct qla_tgt_sess *qla_tgt_sess; + /* Pointer to TCM FC nexus */ + struct se_session *nport_nexus; + /* Returned by tcm_qla2xxx_make_nodeacl() */ + struct se_node_acl se_node_acl; +}; + +struct tcm_qla2xxx_tpg_attrib { + int generate_node_acls; + int cache_dynamic_acls; + int demo_mode_write_protect; + int prod_mode_write_protect; +}; + +struct tcm_qla2xxx_tpg { + /* FC lport target portal group tag for TCM */ + u16 lport_tpgt; + /* Atomic bit to determine TPG active status */ + atomic_t lport_tpg_enabled; + /* Pointer back to tcm_qla2xxx_lport */ + struct tcm_qla2xxx_lport *lport; + /* Used by tcm_qla2xxx_tpg_attrib_cit */ + struct tcm_qla2xxx_tpg_attrib tpg_attrib; + /* Returned by tcm_qla2xxx_make_tpg() */ + struct se_portal_group se_tpg; +}; + +#define QLA_TPG_ATTRIB(tpg) (&(tpg)->tpg_attrib) + +struct tcm_qla2xxx_fc_loopid { + struct se_node_acl *se_nacl; +}; + +struct tcm_qla2xxx_lport { + /* SCSI protocol the lport is providing */ + u8 lport_proto_id; + /* Binary World Wide unique Port Name for FC Target Lport */ + u64 lport_wwpn; + /* Binary World Wide unique Port Name for FC NPIV Target Lport */ + u64 lport_npiv_wwpn; + /* Binary World Wide unique Node Name for FC NPIV Target Lport */ + u64 lport_npiv_wwnn; + /* ASCII formatted WWPN for FC Target Lport */ + char lport_name[TCM_QLA2XXX_NAMELEN]; + /* ASCII formatted WWPN+WWNN for NPIV FC Target Lport */ + char lport_npiv_name[TCM_QLA2XXX_NPIV_NAMELEN]; + /* map for fc_port pointers in 24-bit FC Port ID space */ + struct btree_head32 lport_fcport_map; + /* vmalloc-ed memory for fc_port pointers for 16-bit FC loop ID */ + struct tcm_qla2xxx_fc_loopid *lport_loopid_map; + /* Pointer to struct scsi_qla_host from qla2xxx LLD */ + struct scsi_qla_host *qla_vha; + /* Pointer to struct scsi_qla_host for NPIV VP from qla2xxx LLD */ + struct scsi_qla_host *qla_npiv_vp; + /* Pointer to struct qla_tgt pointer */ + struct qla_tgt lport_qla_tgt; + /* Pointer to struct fc_vport for NPIV vport from libfc */ + struct fc_vport *npiv_vport; + /* Pointer to TPG=1 for non NPIV mode */ + struct tcm_qla2xxx_tpg *tpg_1; + /* Returned by tcm_qla2xxx_make_lport() */ + struct se_wwn lport_wwn; +}; diff --git a/drivers/scsi/qla4xxx/ql4_attr.c b/drivers/scsi/qla4xxx/ql4_attr.c index 0b0a7d42137..c681b2a355e 100644 --- a/drivers/scsi/qla4xxx/ql4_attr.c +++ b/drivers/scsi/qla4xxx/ql4_attr.c @@ -9,6 +9,140 @@ #include "ql4_glbl.h" #include "ql4_dbg.h" +static ssize_t +qla4_8xxx_sysfs_read_fw_dump(struct file *filep, struct kobject *kobj, + struct bin_attribute *ba, char *buf, loff_t off, + size_t count) +{ + struct scsi_qla_host *ha = to_qla_host(dev_to_shost(container_of(kobj, + struct device, kobj))); + + if (!is_qla8022(ha)) + return -EINVAL; + + if (!test_bit(AF_82XX_DUMP_READING, &ha->flags)) + return 0; + + return memory_read_from_buffer(buf, count, &off, ha->fw_dump, + ha->fw_dump_size); +} + +static ssize_t +qla4_8xxx_sysfs_write_fw_dump(struct file *filep, struct kobject *kobj, + struct bin_attribute *ba, char *buf, loff_t off, + size_t count) +{ + struct scsi_qla_host *ha = to_qla_host(dev_to_shost(container_of(kobj, + struct device, kobj))); + uint32_t dev_state; + long reading; + int ret = 0; + + if (!is_qla8022(ha)) + return -EINVAL; + + if (off != 0) + return ret; + + buf[1] = 0; + ret = kstrtol(buf, 10, &reading); + if (ret) { + ql4_printk(KERN_ERR, ha, "%s: Invalid input. Return err %d\n", + __func__, ret); + return ret; + } + + switch (reading) { + case 0: + /* clear dump collection flags */ + if (test_and_clear_bit(AF_82XX_DUMP_READING, &ha->flags)) { + clear_bit(AF_82XX_FW_DUMPED, &ha->flags); + /* Reload minidump template */ + qla4xxx_alloc_fw_dump(ha); + DEBUG2(ql4_printk(KERN_INFO, ha, + "Firmware template reloaded\n")); + } + break; + case 1: + /* Set flag to read dump */ + if (test_bit(AF_82XX_FW_DUMPED, &ha->flags) && + !test_bit(AF_82XX_DUMP_READING, &ha->flags)) { + set_bit(AF_82XX_DUMP_READING, &ha->flags); + DEBUG2(ql4_printk(KERN_INFO, ha, + "Raw firmware dump ready for read on (%ld).\n", + ha->host_no)); + } + break; + case 2: + /* Reset HBA */ + qla4_8xxx_idc_lock(ha); + dev_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DEV_STATE); + if (dev_state == QLA82XX_DEV_READY) { + ql4_printk(KERN_INFO, ha, + "%s: Setting Need reset, reset_owner is 0x%x.\n", + __func__, ha->func_num); + qla4_8xxx_wr_32(ha, QLA82XX_CRB_DEV_STATE, + QLA82XX_DEV_NEED_RESET); + set_bit(AF_82XX_RST_OWNER, &ha->flags); + } else + ql4_printk(KERN_INFO, ha, + "%s: Reset not performed as device state is 0x%x\n", + __func__, dev_state); + + qla4_8xxx_idc_unlock(ha); + break; + default: + /* do nothing */ + break; + } + + return count; +} + +static struct bin_attribute sysfs_fw_dump_attr = { + .attr = { + .name = "fw_dump", + .mode = S_IRUSR | S_IWUSR, + }, + .size = 0, + .read = qla4_8xxx_sysfs_read_fw_dump, + .write = qla4_8xxx_sysfs_write_fw_dump, +}; + +static struct sysfs_entry { + char *name; + struct bin_attribute *attr; +} bin_file_entries[] = { + { "fw_dump", &sysfs_fw_dump_attr }, + { NULL }, +}; + +void qla4_8xxx_alloc_sysfs_attr(struct scsi_qla_host *ha) +{ + struct Scsi_Host *host = ha->host; + struct sysfs_entry *iter; + int ret; + + for (iter = bin_file_entries; iter->name; iter++) { + ret = sysfs_create_bin_file(&host->shost_gendev.kobj, + iter->attr); + if (ret) + ql4_printk(KERN_ERR, ha, + "Unable to create sysfs %s binary attribute (%d).\n", + iter->name, ret); + } +} + +void qla4_8xxx_free_sysfs_attr(struct scsi_qla_host *ha) +{ + struct Scsi_Host *host = ha->host; + struct sysfs_entry *iter; + + for (iter = bin_file_entries; iter->name; iter++) + sysfs_remove_bin_file(&host->shost_gendev.kobj, + iter->attr); +} + /* Scsi_Host attributes. */ static ssize_t qla4xxx_fw_version_show(struct device *dev, diff --git a/drivers/scsi/qla4xxx/ql4_def.h b/drivers/scsi/qla4xxx/ql4_def.h index 7f2492e88be..96a5616a8fd 100644 --- a/drivers/scsi/qla4xxx/ql4_def.h +++ b/drivers/scsi/qla4xxx/ql4_def.h @@ -398,6 +398,16 @@ struct isp_operations { int (*get_sys_info) (struct scsi_qla_host *); }; +struct ql4_mdump_size_table { + uint32_t size; + uint32_t size_cmask_02; + uint32_t size_cmask_04; + uint32_t size_cmask_08; + uint32_t size_cmask_10; + uint32_t size_cmask_FF; + uint32_t version; +}; + /*qla4xxx ipaddress configuration details */ struct ipaddress_config { uint16_t ipv4_options; @@ -485,6 +495,10 @@ struct scsi_qla_host { #define AF_EEH_BUSY 20 /* 0x00100000 */ #define AF_PCI_CHANNEL_IO_PERM_FAILURE 21 /* 0x00200000 */ #define AF_BUILD_DDB_LIST 22 /* 0x00400000 */ +#define AF_82XX_FW_DUMPED 24 /* 0x01000000 */ +#define AF_82XX_RST_OWNER 25 /* 0x02000000 */ +#define AF_82XX_DUMP_READING 26 /* 0x04000000 */ + unsigned long dpc_flags; #define DPC_RESET_HA 1 /* 0x00000002 */ @@ -662,6 +676,11 @@ struct scsi_qla_host { uint32_t nx_dev_init_timeout; uint32_t nx_reset_timeout; + void *fw_dump; + uint32_t fw_dump_size; + uint32_t fw_dump_capture_mask; + void *fw_dump_tmplt_hdr; + uint32_t fw_dump_tmplt_size; struct completion mbx_intr_comp; @@ -936,4 +955,7 @@ static inline int ql4xxx_reset_active(struct scsi_qla_host *ha) #define PROCESS_ALL_AENS 0 #define FLUSH_DDB_CHANGED_AENS 1 +/* Defines for udev events */ +#define QL4_UEVENT_CODE_FW_DUMP 0 + #endif /*_QLA4XXX_H */ diff --git a/drivers/scsi/qla4xxx/ql4_fw.h b/drivers/scsi/qla4xxx/ql4_fw.h index 210cd1d6447..7240948fb92 100644 --- a/drivers/scsi/qla4xxx/ql4_fw.h +++ b/drivers/scsi/qla4xxx/ql4_fw.h @@ -385,6 +385,11 @@ struct qla_flt_region { #define MBOX_CMD_GET_IP_ADDR_STATE 0x0091 #define MBOX_CMD_SEND_IPV6_ROUTER_SOL 0x0092 #define MBOX_CMD_GET_DB_ENTRY_CURRENT_IP_ADDR 0x0093 +#define MBOX_CMD_MINIDUMP 0x0129 + +/* Minidump subcommand */ +#define MINIDUMP_GET_SIZE_SUBCOMMAND 0x00 +#define MINIDUMP_GET_TMPLT_SUBCOMMAND 0x01 /* Mailbox 1 */ #define FW_STATE_READY 0x0000 @@ -1190,4 +1195,27 @@ struct ql_iscsi_stats { uint8_t reserved2[264]; /* 0x0308 - 0x040F */ }; +#define QLA82XX_DBG_STATE_ARRAY_LEN 16 +#define QLA82XX_DBG_CAP_SIZE_ARRAY_LEN 8 +#define QLA82XX_DBG_RSVD_ARRAY_LEN 8 + +struct qla4_8xxx_minidump_template_hdr { + uint32_t entry_type; + uint32_t first_entry_offset; + uint32_t size_of_template; + uint32_t capture_debug_level; + uint32_t num_of_entries; + uint32_t version; + uint32_t driver_timestamp; + uint32_t checksum; + + uint32_t driver_capture_mask; + uint32_t driver_info_word2; + uint32_t driver_info_word3; + uint32_t driver_info_word4; + + uint32_t saved_state_array[QLA82XX_DBG_STATE_ARRAY_LEN]; + uint32_t capture_size_array[QLA82XX_DBG_CAP_SIZE_ARRAY_LEN]; +}; + #endif /* _QLA4X_FW_H */ diff --git a/drivers/scsi/qla4xxx/ql4_glbl.h b/drivers/scsi/qla4xxx/ql4_glbl.h index 910536667cf..20b49d01904 100644 --- a/drivers/scsi/qla4xxx/ql4_glbl.h +++ b/drivers/scsi/qla4xxx/ql4_glbl.h @@ -196,10 +196,18 @@ int qla4xxx_bsg_request(struct bsg_job *bsg_job); int qla4xxx_process_vendor_specific(struct bsg_job *bsg_job); void qla4xxx_arm_relogin_timer(struct ddb_entry *ddb_entry); +int qla4xxx_get_minidump_template(struct scsi_qla_host *ha, + dma_addr_t phys_addr); +int qla4xxx_req_template_size(struct scsi_qla_host *ha); +void qla4_8xxx_alloc_sysfs_attr(struct scsi_qla_host *ha); +void qla4_8xxx_free_sysfs_attr(struct scsi_qla_host *ha); +void qla4xxx_alloc_fw_dump(struct scsi_qla_host *ha); extern int ql4xextended_error_logging; extern int ql4xdontresethba; extern int ql4xenablemsix; +extern int ql4xmdcapmask; +extern int ql4xenablemd; extern struct device_attribute *qla4xxx_host_attrs[]; #endif /* _QLA4x_GBL_H */ diff --git a/drivers/scsi/qla4xxx/ql4_init.c b/drivers/scsi/qla4xxx/ql4_init.c index 90ee5d8fa73..bf36723b84e 100644 --- a/drivers/scsi/qla4xxx/ql4_init.c +++ b/drivers/scsi/qla4xxx/ql4_init.c @@ -277,6 +277,94 @@ qla4xxx_wait_for_ip_config(struct scsi_qla_host *ha) return ipv4_wait|ipv6_wait; } +/** + * qla4xxx_alloc_fw_dump - Allocate memory for minidump data. + * @ha: pointer to host adapter structure. + **/ +void qla4xxx_alloc_fw_dump(struct scsi_qla_host *ha) +{ + int status; + uint32_t capture_debug_level; + int hdr_entry_bit, k; + void *md_tmp; + dma_addr_t md_tmp_dma; + struct qla4_8xxx_minidump_template_hdr *md_hdr; + + if (ha->fw_dump) { + ql4_printk(KERN_WARNING, ha, + "Firmware dump previously allocated.\n"); + return; + } + + status = qla4xxx_req_template_size(ha); + if (status != QLA_SUCCESS) { + ql4_printk(KERN_INFO, ha, + "scsi%ld: Failed to get template size\n", + ha->host_no); + return; + } + + clear_bit(AF_82XX_FW_DUMPED, &ha->flags); + + /* Allocate memory for saving the template */ + md_tmp = dma_alloc_coherent(&ha->pdev->dev, ha->fw_dump_tmplt_size, + &md_tmp_dma, GFP_KERNEL); + + /* Request template */ + status = qla4xxx_get_minidump_template(ha, md_tmp_dma); + if (status != QLA_SUCCESS) { + ql4_printk(KERN_INFO, ha, + "scsi%ld: Failed to get minidump template\n", + ha->host_no); + goto alloc_cleanup; + } + + md_hdr = (struct qla4_8xxx_minidump_template_hdr *)md_tmp; + + capture_debug_level = md_hdr->capture_debug_level; + + /* Get capture mask based on module loadtime setting. */ + if (ql4xmdcapmask >= 0x3 && ql4xmdcapmask <= 0x7F) + ha->fw_dump_capture_mask = ql4xmdcapmask; + else + ha->fw_dump_capture_mask = capture_debug_level; + + md_hdr->driver_capture_mask = ha->fw_dump_capture_mask; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Minimum num of entries = %d\n", + md_hdr->num_of_entries)); + DEBUG2(ql4_printk(KERN_INFO, ha, "Dump template size = %d\n", + ha->fw_dump_tmplt_size)); + DEBUG2(ql4_printk(KERN_INFO, ha, "Selected Capture mask =0x%x\n", + ha->fw_dump_capture_mask)); + + /* Calculate fw_dump_size */ + for (hdr_entry_bit = 0x2, k = 1; (hdr_entry_bit & 0xFF); + hdr_entry_bit <<= 1, k++) { + if (hdr_entry_bit & ha->fw_dump_capture_mask) + ha->fw_dump_size += md_hdr->capture_size_array[k]; + } + + /* Total firmware dump size including command header */ + ha->fw_dump_size += ha->fw_dump_tmplt_size; + ha->fw_dump = vmalloc(ha->fw_dump_size); + if (!ha->fw_dump) + goto alloc_cleanup; + + DEBUG2(ql4_printk(KERN_INFO, ha, + "Minidump Tempalate Size = 0x%x KB\n", + ha->fw_dump_tmplt_size)); + DEBUG2(ql4_printk(KERN_INFO, ha, + "Total Minidump size = 0x%x KB\n", ha->fw_dump_size)); + + memcpy(ha->fw_dump, md_tmp, ha->fw_dump_tmplt_size); + ha->fw_dump_tmplt_hdr = ha->fw_dump; + +alloc_cleanup: + dma_free_coherent(&ha->pdev->dev, ha->fw_dump_tmplt_size, + md_tmp, md_tmp_dma); +} + static int qla4xxx_fw_ready(struct scsi_qla_host *ha) { uint32_t timeout_count; @@ -445,9 +533,13 @@ static int qla4xxx_init_firmware(struct scsi_qla_host *ha) "control block\n", ha->host_no, __func__)); return status; } + if (!qla4xxx_fw_ready(ha)) return status; + if (is_qla8022(ha) && !test_bit(AF_INIT_DONE, &ha->flags)) + qla4xxx_alloc_fw_dump(ha); + return qla4xxx_get_firmware_status(ha); } @@ -884,8 +976,8 @@ int qla4xxx_ddb_change(struct scsi_qla_host *ha, uint32_t fw_ddb_index, switch (state) { case DDB_DS_SESSION_ACTIVE: case DDB_DS_DISCOVERY: - ddb_entry->unblock_sess(ddb_entry->sess); qla4xxx_update_session_conn_param(ha, ddb_entry); + ddb_entry->unblock_sess(ddb_entry->sess); status = QLA_SUCCESS; break; case DDB_DS_SESSION_FAILED: @@ -897,6 +989,7 @@ int qla4xxx_ddb_change(struct scsi_qla_host *ha, uint32_t fw_ddb_index, } break; case DDB_DS_SESSION_ACTIVE: + case DDB_DS_DISCOVERY: switch (state) { case DDB_DS_SESSION_FAILED: /* diff --git a/drivers/scsi/qla4xxx/ql4_mbx.c b/drivers/scsi/qla4xxx/ql4_mbx.c index 7ac21dabbf2..cab8f665a41 100644 --- a/drivers/scsi/qla4xxx/ql4_mbx.c +++ b/drivers/scsi/qla4xxx/ql4_mbx.c @@ -51,25 +51,6 @@ int qla4xxx_mailbox_command(struct scsi_qla_host *ha, uint8_t inCount, } } - if (is_qla8022(ha)) { - if (test_bit(AF_FW_RECOVERY, &ha->flags)) { - DEBUG2(ql4_printk(KERN_WARNING, ha, "scsi%ld: %s: " - "prematurely completing mbx cmd as firmware " - "recovery detected\n", ha->host_no, __func__)); - return status; - } - /* Do not send any mbx cmd if h/w is in failed state*/ - qla4_8xxx_idc_lock(ha); - dev_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DEV_STATE); - qla4_8xxx_idc_unlock(ha); - if (dev_state == QLA82XX_DEV_FAILED) { - ql4_printk(KERN_WARNING, ha, "scsi%ld: %s: H/W is in " - "failed state, do not send any mailbox commands\n", - ha->host_no, __func__); - return status; - } - } - if ((is_aer_supported(ha)) && (test_bit(AF_PCI_CHANNEL_IO_PERM_FAILURE, &ha->flags))) { DEBUG2(printk(KERN_WARNING "scsi%ld: %s: Perm failure on EEH, " @@ -96,6 +77,25 @@ int qla4xxx_mailbox_command(struct scsi_qla_host *ha, uint8_t inCount, msleep(10); } + if (is_qla8022(ha)) { + if (test_bit(AF_FW_RECOVERY, &ha->flags)) { + DEBUG2(ql4_printk(KERN_WARNING, ha, + "scsi%ld: %s: prematurely completing mbx cmd as firmware recovery detected\n", + ha->host_no, __func__)); + goto mbox_exit; + } + /* Do not send any mbx cmd if h/w is in failed state*/ + qla4_8xxx_idc_lock(ha); + dev_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DEV_STATE); + qla4_8xxx_idc_unlock(ha); + if (dev_state == QLA82XX_DEV_FAILED) { + ql4_printk(KERN_WARNING, ha, + "scsi%ld: %s: H/W is in failed state, do not send any mailbox commands\n", + ha->host_no, __func__); + goto mbox_exit; + } + } + spin_lock_irqsave(&ha->hardware_lock, flags); ha->mbox_status_count = outCount; @@ -270,6 +270,79 @@ mbox_exit: return status; } +/** + * qla4xxx_get_minidump_template - Get the firmware template + * @ha: Pointer to host adapter structure. + * @phys_addr: dma address for template + * + * Obtain the minidump template from firmware during initialization + * as it may not be available when minidump is desired. + **/ +int qla4xxx_get_minidump_template(struct scsi_qla_host *ha, + dma_addr_t phys_addr) +{ + uint32_t mbox_cmd[MBOX_REG_COUNT]; + uint32_t mbox_sts[MBOX_REG_COUNT]; + int status; + + memset(&mbox_cmd, 0, sizeof(mbox_cmd)); + memset(&mbox_sts, 0, sizeof(mbox_sts)); + + mbox_cmd[0] = MBOX_CMD_MINIDUMP; + mbox_cmd[1] = MINIDUMP_GET_TMPLT_SUBCOMMAND; + mbox_cmd[2] = LSDW(phys_addr); + mbox_cmd[3] = MSDW(phys_addr); + mbox_cmd[4] = ha->fw_dump_tmplt_size; + mbox_cmd[5] = 0; + + status = qla4xxx_mailbox_command(ha, MBOX_REG_COUNT, 2, &mbox_cmd[0], + &mbox_sts[0]); + if (status != QLA_SUCCESS) { + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi%ld: %s: Cmd = %08X, mbx[0] = 0x%04x, mbx[1] = 0x%04x\n", + ha->host_no, __func__, mbox_cmd[0], + mbox_sts[0], mbox_sts[1])); + } + return status; +} + +/** + * qla4xxx_req_template_size - Get minidump template size from firmware. + * @ha: Pointer to host adapter structure. + **/ +int qla4xxx_req_template_size(struct scsi_qla_host *ha) +{ + uint32_t mbox_cmd[MBOX_REG_COUNT]; + uint32_t mbox_sts[MBOX_REG_COUNT]; + int status; + + memset(&mbox_cmd, 0, sizeof(mbox_cmd)); + memset(&mbox_sts, 0, sizeof(mbox_sts)); + + mbox_cmd[0] = MBOX_CMD_MINIDUMP; + mbox_cmd[1] = MINIDUMP_GET_SIZE_SUBCOMMAND; + + status = qla4xxx_mailbox_command(ha, MBOX_REG_COUNT, 8, &mbox_cmd[0], + &mbox_sts[0]); + if (status == QLA_SUCCESS) { + ha->fw_dump_tmplt_size = mbox_sts[1]; + DEBUG2(ql4_printk(KERN_INFO, ha, + "%s: sts[0]=0x%04x, template size=0x%04x, size_cm_02=0x%04x, size_cm_04=0x%04x, size_cm_08=0x%04x, size_cm_10=0x%04x, size_cm_FF=0x%04x, version=0x%04x\n", + __func__, mbox_sts[0], mbox_sts[1], + mbox_sts[2], mbox_sts[3], mbox_sts[4], + mbox_sts[5], mbox_sts[6], mbox_sts[7])); + if (ha->fw_dump_tmplt_size == 0) + status = QLA_ERROR; + } else { + ql4_printk(KERN_WARNING, ha, + "%s: Error sts[0]=0x%04x, mbx[1]=0x%04x\n", + __func__, mbox_sts[0], mbox_sts[1]); + status = QLA_ERROR; + } + + return status; +} + void qla4xxx_mailbox_premature_completion(struct scsi_qla_host *ha) { set_bit(AF_FW_RECOVERY, &ha->flags); diff --git a/drivers/scsi/qla4xxx/ql4_nx.c b/drivers/scsi/qla4xxx/ql4_nx.c index e1e46b6dac7..228b67020d2 100644 --- a/drivers/scsi/qla4xxx/ql4_nx.c +++ b/drivers/scsi/qla4xxx/ql4_nx.c @@ -7,6 +7,7 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/pci.h> +#include <linux/ratelimit.h> #include "ql4_def.h" #include "ql4_glbl.h" @@ -420,6 +421,38 @@ qla4_8xxx_rd_32(struct scsi_qla_host *ha, ulong off) return data; } +/* Minidump related functions */ +static int qla4_8xxx_md_rw_32(struct scsi_qla_host *ha, uint32_t off, + u32 data, uint8_t flag) +{ + uint32_t win_read, off_value, rval = QLA_SUCCESS; + + off_value = off & 0xFFFF0000; + writel(off_value, (void __iomem *)(CRB_WINDOW_2M + ha->nx_pcibase)); + + /* Read back value to make sure write has gone through before trying + * to use it. + */ + win_read = readl((void __iomem *)(CRB_WINDOW_2M + ha->nx_pcibase)); + if (win_read != off_value) { + DEBUG2(ql4_printk(KERN_INFO, ha, + "%s: Written (0x%x) != Read (0x%x), off=0x%x\n", + __func__, off_value, win_read, off)); + return QLA_ERROR; + } + + off_value = off & 0x0000FFFF; + + if (flag) + writel(data, (void __iomem *)(off_value + CRB_INDIRECT_2M + + ha->nx_pcibase)); + else + rval = readl((void __iomem *)(off_value + CRB_INDIRECT_2M + + ha->nx_pcibase)); + + return rval; +} + #define CRB_WIN_LOCK_TIMEOUT 100000000 int qla4_8xxx_crb_win_lock(struct scsi_qla_host *ha) @@ -1252,9 +1285,9 @@ qla4_8xxx_pci_mem_read_2M(struct scsi_qla_host *ha, } if (j >= MAX_CTL_CHECK) { - if (printk_ratelimit()) - ql4_printk(KERN_ERR, ha, - "failed to read through agent\n"); + printk_ratelimited(KERN_ERR + "%s: failed to read through agent\n", + __func__); break; } @@ -1390,7 +1423,8 @@ qla4_8xxx_pci_mem_write_2M(struct scsi_qla_host *ha, if (j >= MAX_CTL_CHECK) { if (printk_ratelimit()) ql4_printk(KERN_ERR, ha, - "failed to write through agent\n"); + "%s: failed to read through agent\n", + __func__); ret = -1; break; } @@ -1462,6 +1496,8 @@ qla4_8xxx_set_drv_active(struct scsi_qla_host *ha) drv_active = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DRV_ACTIVE); drv_active |= (1 << (ha->func_num * 4)); + ql4_printk(KERN_INFO, ha, "%s(%ld): drv_active: 0x%08x\n", + __func__, ha->host_no, drv_active); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DRV_ACTIVE, drv_active); } @@ -1472,6 +1508,8 @@ qla4_8xxx_clear_drv_active(struct scsi_qla_host *ha) drv_active = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DRV_ACTIVE); drv_active &= ~(1 << (ha->func_num * 4)); + ql4_printk(KERN_INFO, ha, "%s(%ld): drv_active: 0x%08x\n", + __func__, ha->host_no, drv_active); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DRV_ACTIVE, drv_active); } @@ -1497,6 +1535,8 @@ qla4_8xxx_set_rst_ready(struct scsi_qla_host *ha) drv_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DRV_STATE); drv_state |= (1 << (ha->func_num * 4)); + ql4_printk(KERN_INFO, ha, "%s(%ld): drv_state: 0x%08x\n", + __func__, ha->host_no, drv_state); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DRV_STATE, drv_state); } @@ -1507,6 +1547,8 @@ qla4_8xxx_clear_rst_ready(struct scsi_qla_host *ha) drv_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DRV_STATE); drv_state &= ~(1 << (ha->func_num * 4)); + ql4_printk(KERN_INFO, ha, "%s(%ld): drv_state: 0x%08x\n", + __func__, ha->host_no, drv_state); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DRV_STATE, drv_state); } @@ -1601,6 +1643,629 @@ static void qla4_8xxx_rom_lock_recovery(struct scsi_qla_host *ha) qla4_8xxx_rom_unlock(ha); } +static void qla4_8xxx_minidump_process_rdcrb(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t r_addr, r_stride, loop_cnt, i, r_value; + struct qla82xx_minidump_entry_crb *crb_hdr; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + crb_hdr = (struct qla82xx_minidump_entry_crb *)entry_hdr; + r_addr = crb_hdr->addr; + r_stride = crb_hdr->crb_strd.addr_stride; + loop_cnt = crb_hdr->op_count; + + for (i = 0; i < loop_cnt; i++) { + r_value = qla4_8xxx_md_rw_32(ha, r_addr, 0, 0); + *data_ptr++ = cpu_to_le32(r_addr); + *data_ptr++ = cpu_to_le32(r_value); + r_addr += r_stride; + } + *d_ptr = data_ptr; +} + +static int qla4_8xxx_minidump_process_l2tag(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t addr, r_addr, c_addr, t_r_addr; + uint32_t i, k, loop_count, t_value, r_cnt, r_value; + unsigned long p_wait, w_time, p_mask; + uint32_t c_value_w, c_value_r; + struct qla82xx_minidump_entry_cache *cache_hdr; + int rval = QLA_ERROR; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + cache_hdr = (struct qla82xx_minidump_entry_cache *)entry_hdr; + + loop_count = cache_hdr->op_count; + r_addr = cache_hdr->read_addr; + c_addr = cache_hdr->control_addr; + c_value_w = cache_hdr->cache_ctrl.write_value; + + t_r_addr = cache_hdr->tag_reg_addr; + t_value = cache_hdr->addr_ctrl.init_tag_value; + r_cnt = cache_hdr->read_ctrl.read_addr_cnt; + p_wait = cache_hdr->cache_ctrl.poll_wait; + p_mask = cache_hdr->cache_ctrl.poll_mask; + + for (i = 0; i < loop_count; i++) { + qla4_8xxx_md_rw_32(ha, t_r_addr, t_value, 1); + + if (c_value_w) + qla4_8xxx_md_rw_32(ha, c_addr, c_value_w, 1); + + if (p_mask) { + w_time = jiffies + p_wait; + do { + c_value_r = qla4_8xxx_md_rw_32(ha, c_addr, + 0, 0); + if ((c_value_r & p_mask) == 0) { + break; + } else if (time_after_eq(jiffies, w_time)) { + /* capturing dump failed */ + return rval; + } + } while (1); + } + + addr = r_addr; + for (k = 0; k < r_cnt; k++) { + r_value = qla4_8xxx_md_rw_32(ha, addr, 0, 0); + *data_ptr++ = cpu_to_le32(r_value); + addr += cache_hdr->read_ctrl.read_addr_stride; + } + + t_value += cache_hdr->addr_ctrl.tag_value_stride; + } + *d_ptr = data_ptr; + return QLA_SUCCESS; +} + +static int qla4_8xxx_minidump_process_control(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr) +{ + struct qla82xx_minidump_entry_crb *crb_entry; + uint32_t read_value, opcode, poll_time, addr, index, rval = QLA_SUCCESS; + uint32_t crb_addr; + unsigned long wtime; + struct qla4_8xxx_minidump_template_hdr *tmplt_hdr; + int i; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + tmplt_hdr = (struct qla4_8xxx_minidump_template_hdr *) + ha->fw_dump_tmplt_hdr; + crb_entry = (struct qla82xx_minidump_entry_crb *)entry_hdr; + + crb_addr = crb_entry->addr; + for (i = 0; i < crb_entry->op_count; i++) { + opcode = crb_entry->crb_ctrl.opcode; + if (opcode & QLA82XX_DBG_OPCODE_WR) { + qla4_8xxx_md_rw_32(ha, crb_addr, + crb_entry->value_1, 1); + opcode &= ~QLA82XX_DBG_OPCODE_WR; + } + if (opcode & QLA82XX_DBG_OPCODE_RW) { + read_value = qla4_8xxx_md_rw_32(ha, crb_addr, 0, 0); + qla4_8xxx_md_rw_32(ha, crb_addr, read_value, 1); + opcode &= ~QLA82XX_DBG_OPCODE_RW; + } + if (opcode & QLA82XX_DBG_OPCODE_AND) { + read_value = qla4_8xxx_md_rw_32(ha, crb_addr, 0, 0); + read_value &= crb_entry->value_2; + opcode &= ~QLA82XX_DBG_OPCODE_AND; + if (opcode & QLA82XX_DBG_OPCODE_OR) { + read_value |= crb_entry->value_3; + opcode &= ~QLA82XX_DBG_OPCODE_OR; + } + qla4_8xxx_md_rw_32(ha, crb_addr, read_value, 1); + } + if (opcode & QLA82XX_DBG_OPCODE_OR) { + read_value = qla4_8xxx_md_rw_32(ha, crb_addr, 0, 0); + read_value |= crb_entry->value_3; + qla4_8xxx_md_rw_32(ha, crb_addr, read_value, 1); + opcode &= ~QLA82XX_DBG_OPCODE_OR; + } + if (opcode & QLA82XX_DBG_OPCODE_POLL) { + poll_time = crb_entry->crb_strd.poll_timeout; + wtime = jiffies + poll_time; + read_value = qla4_8xxx_md_rw_32(ha, crb_addr, 0, 0); + + do { + if ((read_value & crb_entry->value_2) == + crb_entry->value_1) + break; + else if (time_after_eq(jiffies, wtime)) { + /* capturing dump failed */ + rval = QLA_ERROR; + break; + } else + read_value = qla4_8xxx_md_rw_32(ha, + crb_addr, 0, 0); + } while (1); + opcode &= ~QLA82XX_DBG_OPCODE_POLL; + } + + if (opcode & QLA82XX_DBG_OPCODE_RDSTATE) { + if (crb_entry->crb_strd.state_index_a) { + index = crb_entry->crb_strd.state_index_a; + addr = tmplt_hdr->saved_state_array[index]; + } else { + addr = crb_addr; + } + + read_value = qla4_8xxx_md_rw_32(ha, addr, 0, 0); + index = crb_entry->crb_ctrl.state_index_v; + tmplt_hdr->saved_state_array[index] = read_value; + opcode &= ~QLA82XX_DBG_OPCODE_RDSTATE; + } + + if (opcode & QLA82XX_DBG_OPCODE_WRSTATE) { + if (crb_entry->crb_strd.state_index_a) { + index = crb_entry->crb_strd.state_index_a; + addr = tmplt_hdr->saved_state_array[index]; + } else { + addr = crb_addr; + } + + if (crb_entry->crb_ctrl.state_index_v) { + index = crb_entry->crb_ctrl.state_index_v; + read_value = + tmplt_hdr->saved_state_array[index]; + } else { + read_value = crb_entry->value_1; + } + + qla4_8xxx_md_rw_32(ha, addr, read_value, 1); + opcode &= ~QLA82XX_DBG_OPCODE_WRSTATE; + } + + if (opcode & QLA82XX_DBG_OPCODE_MDSTATE) { + index = crb_entry->crb_ctrl.state_index_v; + read_value = tmplt_hdr->saved_state_array[index]; + read_value <<= crb_entry->crb_ctrl.shl; + read_value >>= crb_entry->crb_ctrl.shr; + if (crb_entry->value_2) + read_value &= crb_entry->value_2; + read_value |= crb_entry->value_3; + read_value += crb_entry->value_1; + tmplt_hdr->saved_state_array[index] = read_value; + opcode &= ~QLA82XX_DBG_OPCODE_MDSTATE; + } + crb_addr += crb_entry->crb_strd.addr_stride; + } + DEBUG2(ql4_printk(KERN_INFO, ha, "Leaving fn: %s\n", __func__)); + return rval; +} + +static void qla4_8xxx_minidump_process_rdocm(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t r_addr, r_stride, loop_cnt, i, r_value; + struct qla82xx_minidump_entry_rdocm *ocm_hdr; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + ocm_hdr = (struct qla82xx_minidump_entry_rdocm *)entry_hdr; + r_addr = ocm_hdr->read_addr; + r_stride = ocm_hdr->read_addr_stride; + loop_cnt = ocm_hdr->op_count; + + DEBUG2(ql4_printk(KERN_INFO, ha, + "[%s]: r_addr: 0x%x, r_stride: 0x%x, loop_cnt: 0x%x\n", + __func__, r_addr, r_stride, loop_cnt)); + + for (i = 0; i < loop_cnt; i++) { + r_value = readl((void __iomem *)(r_addr + ha->nx_pcibase)); + *data_ptr++ = cpu_to_le32(r_value); + r_addr += r_stride; + } + DEBUG2(ql4_printk(KERN_INFO, ha, "Leaving fn: %s datacount: 0x%lx\n", + __func__, (loop_cnt * sizeof(uint32_t)))); + *d_ptr = data_ptr; +} + +static void qla4_8xxx_minidump_process_rdmux(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t r_addr, s_stride, s_addr, s_value, loop_cnt, i, r_value; + struct qla82xx_minidump_entry_mux *mux_hdr; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + mux_hdr = (struct qla82xx_minidump_entry_mux *)entry_hdr; + r_addr = mux_hdr->read_addr; + s_addr = mux_hdr->select_addr; + s_stride = mux_hdr->select_value_stride; + s_value = mux_hdr->select_value; + loop_cnt = mux_hdr->op_count; + + for (i = 0; i < loop_cnt; i++) { + qla4_8xxx_md_rw_32(ha, s_addr, s_value, 1); + r_value = qla4_8xxx_md_rw_32(ha, r_addr, 0, 0); + *data_ptr++ = cpu_to_le32(s_value); + *data_ptr++ = cpu_to_le32(r_value); + s_value += s_stride; + } + *d_ptr = data_ptr; +} + +static void qla4_8xxx_minidump_process_l1cache(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t addr, r_addr, c_addr, t_r_addr; + uint32_t i, k, loop_count, t_value, r_cnt, r_value; + uint32_t c_value_w; + struct qla82xx_minidump_entry_cache *cache_hdr; + uint32_t *data_ptr = *d_ptr; + + cache_hdr = (struct qla82xx_minidump_entry_cache *)entry_hdr; + loop_count = cache_hdr->op_count; + r_addr = cache_hdr->read_addr; + c_addr = cache_hdr->control_addr; + c_value_w = cache_hdr->cache_ctrl.write_value; + + t_r_addr = cache_hdr->tag_reg_addr; + t_value = cache_hdr->addr_ctrl.init_tag_value; + r_cnt = cache_hdr->read_ctrl.read_addr_cnt; + + for (i = 0; i < loop_count; i++) { + qla4_8xxx_md_rw_32(ha, t_r_addr, t_value, 1); + qla4_8xxx_md_rw_32(ha, c_addr, c_value_w, 1); + addr = r_addr; + for (k = 0; k < r_cnt; k++) { + r_value = qla4_8xxx_md_rw_32(ha, addr, 0, 0); + *data_ptr++ = cpu_to_le32(r_value); + addr += cache_hdr->read_ctrl.read_addr_stride; + } + t_value += cache_hdr->addr_ctrl.tag_value_stride; + } + *d_ptr = data_ptr; +} + +static void qla4_8xxx_minidump_process_queue(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t s_addr, r_addr; + uint32_t r_stride, r_value, r_cnt, qid = 0; + uint32_t i, k, loop_cnt; + struct qla82xx_minidump_entry_queue *q_hdr; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + q_hdr = (struct qla82xx_minidump_entry_queue *)entry_hdr; + s_addr = q_hdr->select_addr; + r_cnt = q_hdr->rd_strd.read_addr_cnt; + r_stride = q_hdr->rd_strd.read_addr_stride; + loop_cnt = q_hdr->op_count; + + for (i = 0; i < loop_cnt; i++) { + qla4_8xxx_md_rw_32(ha, s_addr, qid, 1); + r_addr = q_hdr->read_addr; + for (k = 0; k < r_cnt; k++) { + r_value = qla4_8xxx_md_rw_32(ha, r_addr, 0, 0); + *data_ptr++ = cpu_to_le32(r_value); + r_addr += r_stride; + } + qid += q_hdr->q_strd.queue_id_stride; + } + *d_ptr = data_ptr; +} + +#define MD_DIRECT_ROM_WINDOW 0x42110030 +#define MD_DIRECT_ROM_READ_BASE 0x42150000 + +static void qla4_8xxx_minidump_process_rdrom(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t r_addr, r_value; + uint32_t i, loop_cnt; + struct qla82xx_minidump_entry_rdrom *rom_hdr; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + rom_hdr = (struct qla82xx_minidump_entry_rdrom *)entry_hdr; + r_addr = rom_hdr->read_addr; + loop_cnt = rom_hdr->read_data_size/sizeof(uint32_t); + + DEBUG2(ql4_printk(KERN_INFO, ha, + "[%s]: flash_addr: 0x%x, read_data_size: 0x%x\n", + __func__, r_addr, loop_cnt)); + + for (i = 0; i < loop_cnt; i++) { + qla4_8xxx_md_rw_32(ha, MD_DIRECT_ROM_WINDOW, + (r_addr & 0xFFFF0000), 1); + r_value = qla4_8xxx_md_rw_32(ha, + MD_DIRECT_ROM_READ_BASE + + (r_addr & 0x0000FFFF), 0, 0); + *data_ptr++ = cpu_to_le32(r_value); + r_addr += sizeof(uint32_t); + } + *d_ptr = data_ptr; +} + +#define MD_MIU_TEST_AGT_CTRL 0x41000090 +#define MD_MIU_TEST_AGT_ADDR_LO 0x41000094 +#define MD_MIU_TEST_AGT_ADDR_HI 0x41000098 + +static int qla4_8xxx_minidump_process_rdmem(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + uint32_t **d_ptr) +{ + uint32_t r_addr, r_value, r_data; + uint32_t i, j, loop_cnt; + struct qla82xx_minidump_entry_rdmem *m_hdr; + unsigned long flags; + uint32_t *data_ptr = *d_ptr; + + DEBUG2(ql4_printk(KERN_INFO, ha, "Entering fn: %s\n", __func__)); + m_hdr = (struct qla82xx_minidump_entry_rdmem *)entry_hdr; + r_addr = m_hdr->read_addr; + loop_cnt = m_hdr->read_data_size/16; + + DEBUG2(ql4_printk(KERN_INFO, ha, + "[%s]: Read addr: 0x%x, read_data_size: 0x%x\n", + __func__, r_addr, m_hdr->read_data_size)); + + if (r_addr & 0xf) { + DEBUG2(ql4_printk(KERN_INFO, ha, + "[%s]: Read addr 0x%x not 16 bytes alligned\n", + __func__, r_addr)); + return QLA_ERROR; + } + + if (m_hdr->read_data_size % 16) { + DEBUG2(ql4_printk(KERN_INFO, ha, + "[%s]: Read data[0x%x] not multiple of 16 bytes\n", + __func__, m_hdr->read_data_size)); + return QLA_ERROR; + } + + DEBUG2(ql4_printk(KERN_INFO, ha, + "[%s]: rdmem_addr: 0x%x, read_data_size: 0x%x, loop_cnt: 0x%x\n", + __func__, r_addr, m_hdr->read_data_size, loop_cnt)); + + write_lock_irqsave(&ha->hw_lock, flags); + for (i = 0; i < loop_cnt; i++) { + qla4_8xxx_md_rw_32(ha, MD_MIU_TEST_AGT_ADDR_LO, r_addr, 1); + r_value = 0; + qla4_8xxx_md_rw_32(ha, MD_MIU_TEST_AGT_ADDR_HI, r_value, 1); + r_value = MIU_TA_CTL_ENABLE; + qla4_8xxx_md_rw_32(ha, MD_MIU_TEST_AGT_CTRL, r_value, 1); + r_value = MIU_TA_CTL_START | MIU_TA_CTL_ENABLE; + qla4_8xxx_md_rw_32(ha, MD_MIU_TEST_AGT_CTRL, r_value, 1); + + for (j = 0; j < MAX_CTL_CHECK; j++) { + r_value = qla4_8xxx_md_rw_32(ha, MD_MIU_TEST_AGT_CTRL, + 0, 0); + if ((r_value & MIU_TA_CTL_BUSY) == 0) + break; + } + + if (j >= MAX_CTL_CHECK) { + printk_ratelimited(KERN_ERR + "%s: failed to read through agent\n", + __func__); + write_unlock_irqrestore(&ha->hw_lock, flags); + return QLA_SUCCESS; + } + + for (j = 0; j < 4; j++) { + r_data = qla4_8xxx_md_rw_32(ha, + MD_MIU_TEST_AGT_RDDATA[j], + 0, 0); + *data_ptr++ = cpu_to_le32(r_data); + } + + r_addr += 16; + } + write_unlock_irqrestore(&ha->hw_lock, flags); + + DEBUG2(ql4_printk(KERN_INFO, ha, "Leaving fn: %s datacount: 0x%x\n", + __func__, (loop_cnt * 16))); + + *d_ptr = data_ptr; + return QLA_SUCCESS; +} + +static void ql4_8xxx_mark_entry_skipped(struct scsi_qla_host *ha, + struct qla82xx_minidump_entry_hdr *entry_hdr, + int index) +{ + entry_hdr->d_ctrl.driver_flags |= QLA82XX_DBG_SKIPPED_FLAG; + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi(%ld): Skipping entry[%d]: ETYPE[0x%x]-ELEVEL[0x%x]\n", + ha->host_no, index, entry_hdr->entry_type, + entry_hdr->d_ctrl.entry_capture_mask)); +} + +/** + * qla82xx_collect_md_data - Retrieve firmware minidump data. + * @ha: pointer to adapter structure + **/ +static int qla4_8xxx_collect_md_data(struct scsi_qla_host *ha) +{ + int num_entry_hdr = 0; + struct qla82xx_minidump_entry_hdr *entry_hdr; + struct qla4_8xxx_minidump_template_hdr *tmplt_hdr; + uint32_t *data_ptr; + uint32_t data_collected = 0; + int i, rval = QLA_ERROR; + uint64_t now; + uint32_t timestamp; + + if (!ha->fw_dump) { + ql4_printk(KERN_INFO, ha, "%s(%ld) No buffer to dump\n", + __func__, ha->host_no); + return rval; + } + + tmplt_hdr = (struct qla4_8xxx_minidump_template_hdr *) + ha->fw_dump_tmplt_hdr; + data_ptr = (uint32_t *)((uint8_t *)ha->fw_dump + + ha->fw_dump_tmplt_size); + data_collected += ha->fw_dump_tmplt_size; + + num_entry_hdr = tmplt_hdr->num_of_entries; + ql4_printk(KERN_INFO, ha, "[%s]: starting data ptr: %p\n", + __func__, data_ptr); + ql4_printk(KERN_INFO, ha, + "[%s]: no of entry headers in Template: 0x%x\n", + __func__, num_entry_hdr); + ql4_printk(KERN_INFO, ha, "[%s]: Capture Mask obtained: 0x%x\n", + __func__, ha->fw_dump_capture_mask); + ql4_printk(KERN_INFO, ha, "[%s]: Total_data_size 0x%x, %d obtained\n", + __func__, ha->fw_dump_size, ha->fw_dump_size); + + /* Update current timestamp before taking dump */ + now = get_jiffies_64(); + timestamp = (u32)(jiffies_to_msecs(now) / 1000); + tmplt_hdr->driver_timestamp = timestamp; + + entry_hdr = (struct qla82xx_minidump_entry_hdr *) + (((uint8_t *)ha->fw_dump_tmplt_hdr) + + tmplt_hdr->first_entry_offset); + + /* Walk through the entry headers - validate/perform required action */ + for (i = 0; i < num_entry_hdr; i++) { + if (data_collected >= ha->fw_dump_size) { + ql4_printk(KERN_INFO, ha, + "Data collected: [0x%x], Total Dump size: [0x%x]\n", + data_collected, ha->fw_dump_size); + return rval; + } + + if (!(entry_hdr->d_ctrl.entry_capture_mask & + ha->fw_dump_capture_mask)) { + entry_hdr->d_ctrl.driver_flags |= + QLA82XX_DBG_SKIPPED_FLAG; + goto skip_nxt_entry; + } + + DEBUG2(ql4_printk(KERN_INFO, ha, + "Data collected: [0x%x], Dump size left:[0x%x]\n", + data_collected, + (ha->fw_dump_size - data_collected))); + + /* Decode the entry type and take required action to capture + * debug data + */ + switch (entry_hdr->entry_type) { + case QLA82XX_RDEND: + ql4_8xxx_mark_entry_skipped(ha, entry_hdr, i); + break; + case QLA82XX_CNTRL: + rval = qla4_8xxx_minidump_process_control(ha, + entry_hdr); + if (rval != QLA_SUCCESS) { + ql4_8xxx_mark_entry_skipped(ha, entry_hdr, i); + goto md_failed; + } + break; + case QLA82XX_RDCRB: + qla4_8xxx_minidump_process_rdcrb(ha, entry_hdr, + &data_ptr); + break; + case QLA82XX_RDMEM: + rval = qla4_8xxx_minidump_process_rdmem(ha, entry_hdr, + &data_ptr); + if (rval != QLA_SUCCESS) { + ql4_8xxx_mark_entry_skipped(ha, entry_hdr, i); + goto md_failed; + } + break; + case QLA82XX_BOARD: + case QLA82XX_RDROM: + qla4_8xxx_minidump_process_rdrom(ha, entry_hdr, + &data_ptr); + break; + case QLA82XX_L2DTG: + case QLA82XX_L2ITG: + case QLA82XX_L2DAT: + case QLA82XX_L2INS: + rval = qla4_8xxx_minidump_process_l2tag(ha, entry_hdr, + &data_ptr); + if (rval != QLA_SUCCESS) { + ql4_8xxx_mark_entry_skipped(ha, entry_hdr, i); + goto md_failed; + } + break; + case QLA82XX_L1DAT: + case QLA82XX_L1INS: + qla4_8xxx_minidump_process_l1cache(ha, entry_hdr, + &data_ptr); + break; + case QLA82XX_RDOCM: + qla4_8xxx_minidump_process_rdocm(ha, entry_hdr, + &data_ptr); + break; + case QLA82XX_RDMUX: + qla4_8xxx_minidump_process_rdmux(ha, entry_hdr, + &data_ptr); + break; + case QLA82XX_QUEUE: + qla4_8xxx_minidump_process_queue(ha, entry_hdr, + &data_ptr); + break; + case QLA82XX_RDNOP: + default: + ql4_8xxx_mark_entry_skipped(ha, entry_hdr, i); + break; + } + + data_collected = (uint8_t *)data_ptr - + ((uint8_t *)((uint8_t *)ha->fw_dump + + ha->fw_dump_tmplt_size)); +skip_nxt_entry: + /* next entry in the template */ + entry_hdr = (struct qla82xx_minidump_entry_hdr *) + (((uint8_t *)entry_hdr) + + entry_hdr->entry_size); + } + + if ((data_collected + ha->fw_dump_tmplt_size) != ha->fw_dump_size) { + ql4_printk(KERN_INFO, ha, + "Dump data mismatch: Data collected: [0x%x], total_data_size:[0x%x]\n", + data_collected, ha->fw_dump_size); + goto md_failed; + } + + DEBUG2(ql4_printk(KERN_INFO, ha, "Leaving fn: %s Last entry: 0x%x\n", + __func__, i)); +md_failed: + return rval; +} + +/** + * qla4_8xxx_uevent_emit - Send uevent when the firmware dump is ready. + * @ha: pointer to adapter structure + **/ +static void qla4_8xxx_uevent_emit(struct scsi_qla_host *ha, u32 code) +{ + char event_string[40]; + char *envp[] = { event_string, NULL }; + + switch (code) { + case QL4_UEVENT_CODE_FW_DUMP: + snprintf(event_string, sizeof(event_string), "FW_DUMP=%ld", + ha->host_no); + break; + default: + /*do nothing*/ + break; + } + + kobject_uevent_env(&(&ha->pdev->dev)->kobj, KOBJ_CHANGE, envp); +} + /** * qla4_8xxx_device_bootstrap - Initialize device, set DEV_READY, start fw * @ha: pointer to adapter structure @@ -1659,6 +2324,15 @@ dev_initialize: qla4_8xxx_wr_32(ha, QLA82XX_CRB_DRV_IDC_VERSION, QLA82XX_IDC_VERSION); qla4_8xxx_idc_unlock(ha); + if (ql4xenablemd && test_bit(AF_FW_RECOVERY, &ha->flags) && + !test_and_set_bit(AF_82XX_FW_DUMPED, &ha->flags)) { + if (!qla4_8xxx_collect_md_data(ha)) { + qla4_8xxx_uevent_emit(ha, QL4_UEVENT_CODE_FW_DUMP); + } else { + ql4_printk(KERN_INFO, ha, "Unable to collect minidump\n"); + clear_bit(AF_82XX_FW_DUMPED, &ha->flags); + } + } rval = qla4_8xxx_try_start_fw(ha); qla4_8xxx_idc_lock(ha); @@ -1686,6 +2360,7 @@ static void qla4_8xxx_need_reset_handler(struct scsi_qla_host *ha) { uint32_t dev_state, drv_state, drv_active; + uint32_t active_mask = 0xFFFFFFFF; unsigned long reset_timeout; ql4_printk(KERN_INFO, ha, @@ -1697,7 +2372,14 @@ qla4_8xxx_need_reset_handler(struct scsi_qla_host *ha) qla4_8xxx_idc_lock(ha); } - qla4_8xxx_set_rst_ready(ha); + if (!test_bit(AF_82XX_RST_OWNER, &ha->flags)) { + DEBUG2(ql4_printk(KERN_INFO, ha, + "%s(%ld): reset acknowledged\n", + __func__, ha->host_no)); + qla4_8xxx_set_rst_ready(ha); + } else { + active_mask = (~(1 << (ha->func_num * 4))); + } /* wait for 10 seconds for reset ack from all functions */ reset_timeout = jiffies + (ha->nx_reset_timeout * HZ); @@ -1709,12 +2391,24 @@ qla4_8xxx_need_reset_handler(struct scsi_qla_host *ha) "%s(%ld): drv_state = 0x%x, drv_active = 0x%x\n", __func__, ha->host_no, drv_state, drv_active); - while (drv_state != drv_active) { + while (drv_state != (drv_active & active_mask)) { if (time_after_eq(jiffies, reset_timeout)) { - printk("%s: RESET TIMEOUT!\n", DRIVER_NAME); + ql4_printk(KERN_INFO, ha, + "%s: RESET TIMEOUT! drv_state: 0x%08x, drv_active: 0x%08x\n", + DRIVER_NAME, drv_state, drv_active); break; } + /* + * When reset_owner times out, check which functions + * acked/did not ack + */ + if (test_bit(AF_82XX_RST_OWNER, &ha->flags)) { + ql4_printk(KERN_INFO, ha, + "%s(%ld): drv_state = 0x%x, drv_active = 0x%x\n", + __func__, ha->host_no, drv_state, + drv_active); + } qla4_8xxx_idc_unlock(ha); msleep(1000); qla4_8xxx_idc_lock(ha); @@ -1723,14 +2417,18 @@ qla4_8xxx_need_reset_handler(struct scsi_qla_host *ha) drv_active = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DRV_ACTIVE); } + /* Clear RESET OWNER as we are not going to use it any further */ + clear_bit(AF_82XX_RST_OWNER, &ha->flags); + dev_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DEV_STATE); - ql4_printk(KERN_INFO, ha, "3:Device state is 0x%x = %s\n", dev_state, - dev_state < MAX_STATES ? qdev_state[dev_state] : "Unknown"); + ql4_printk(KERN_INFO, ha, "Device state is 0x%x = %s\n", dev_state, + dev_state < MAX_STATES ? qdev_state[dev_state] : "Unknown"); /* Force to DEV_COLD unless someone else is starting a reset */ if (dev_state != QLA82XX_DEV_INITIALIZING) { ql4_printk(KERN_INFO, ha, "HW State: COLD/RE-INIT\n"); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DEV_STATE, QLA82XX_DEV_COLD); + qla4_8xxx_set_rst_ready(ha); } } @@ -1765,8 +2463,9 @@ int qla4_8xxx_device_state_handler(struct scsi_qla_host *ha) } dev_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DEV_STATE); - ql4_printk(KERN_INFO, ha, "1:Device state is 0x%x = %s\n", dev_state, - dev_state < MAX_STATES ? qdev_state[dev_state] : "Unknown"); + DEBUG2(ql4_printk(KERN_INFO, ha, "Device state is 0x%x = %s\n", + dev_state, dev_state < MAX_STATES ? + qdev_state[dev_state] : "Unknown")); /* wait for 30 seconds for device to go ready */ dev_init_timeout = jiffies + (ha->nx_dev_init_timeout * HZ); @@ -1775,15 +2474,19 @@ int qla4_8xxx_device_state_handler(struct scsi_qla_host *ha) while (1) { if (time_after_eq(jiffies, dev_init_timeout)) { - ql4_printk(KERN_WARNING, ha, "Device init failed!\n"); + ql4_printk(KERN_WARNING, ha, + "%s: Device Init Failed 0x%x = %s\n", + DRIVER_NAME, + dev_state, dev_state < MAX_STATES ? + qdev_state[dev_state] : "Unknown"); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DEV_STATE, QLA82XX_DEV_FAILED); } dev_state = qla4_8xxx_rd_32(ha, QLA82XX_CRB_DEV_STATE); - ql4_printk(KERN_INFO, ha, - "2:Device state is 0x%x = %s\n", dev_state, - dev_state < MAX_STATES ? qdev_state[dev_state] : "Unknown"); + ql4_printk(KERN_INFO, ha, "Device state is 0x%x = %s\n", + dev_state, dev_state < MAX_STATES ? + qdev_state[dev_state] : "Unknown"); /* NOTE: Make sure idc unlocked upon exit of switch statement */ switch (dev_state) { @@ -2184,6 +2887,7 @@ qla4_8xxx_isp_reset(struct scsi_qla_host *ha) ql4_printk(KERN_INFO, ha, "HW State: NEED RESET\n"); qla4_8xxx_wr_32(ha, QLA82XX_CRB_DEV_STATE, QLA82XX_DEV_NEED_RESET); + set_bit(AF_82XX_RST_OWNER, &ha->flags); } else ql4_printk(KERN_INFO, ha, "HW State: DEVICE INITIALIZING\n"); @@ -2195,8 +2899,10 @@ qla4_8xxx_isp_reset(struct scsi_qla_host *ha) qla4_8xxx_clear_rst_ready(ha); qla4_8xxx_idc_unlock(ha); - if (rval == QLA_SUCCESS) + if (rval == QLA_SUCCESS) { + ql4_printk(KERN_INFO, ha, "Clearing AF_RECOVERY in qla4_8xxx_isp_reset\n"); clear_bit(AF_FW_RECOVERY, &ha->flags); + } return rval; } diff --git a/drivers/scsi/qla4xxx/ql4_nx.h b/drivers/scsi/qla4xxx/ql4_nx.h index dc7500e47b8..30258479f10 100644 --- a/drivers/scsi/qla4xxx/ql4_nx.h +++ b/drivers/scsi/qla4xxx/ql4_nx.h @@ -792,4 +792,196 @@ struct crb_addr_pair { #define MIU_TEST_AGT_WRDATA_UPPER_LO (0x0b0) #define MIU_TEST_AGT_WRDATA_UPPER_HI (0x0b4) +/* Minidump related */ + +/* Entry Type Defines */ +#define QLA82XX_RDNOP 0 +#define QLA82XX_RDCRB 1 +#define QLA82XX_RDMUX 2 +#define QLA82XX_QUEUE 3 +#define QLA82XX_BOARD 4 +#define QLA82XX_RDOCM 6 +#define QLA82XX_PREGS 7 +#define QLA82XX_L1DTG 8 +#define QLA82XX_L1ITG 9 +#define QLA82XX_L1DAT 11 +#define QLA82XX_L1INS 12 +#define QLA82XX_L2DTG 21 +#define QLA82XX_L2ITG 22 +#define QLA82XX_L2DAT 23 +#define QLA82XX_L2INS 24 +#define QLA82XX_RDROM 71 +#define QLA82XX_RDMEM 72 +#define QLA82XX_CNTRL 98 +#define QLA82XX_RDEND 255 + +/* Opcodes for Control Entries. + * These Flags are bit fields. + */ +#define QLA82XX_DBG_OPCODE_WR 0x01 +#define QLA82XX_DBG_OPCODE_RW 0x02 +#define QLA82XX_DBG_OPCODE_AND 0x04 +#define QLA82XX_DBG_OPCODE_OR 0x08 +#define QLA82XX_DBG_OPCODE_POLL 0x10 +#define QLA82XX_DBG_OPCODE_RDSTATE 0x20 +#define QLA82XX_DBG_OPCODE_WRSTATE 0x40 +#define QLA82XX_DBG_OPCODE_MDSTATE 0x80 + +/* Driver Flags */ +#define QLA82XX_DBG_SKIPPED_FLAG 0x80 /* driver skipped this entry */ +#define QLA82XX_DBG_SIZE_ERR_FLAG 0x40 /* Entry vs Capture size + * mismatch */ + +/* Driver_code is for driver to write some info about the entry + * currently not used. + */ +struct qla82xx_minidump_entry_hdr { + uint32_t entry_type; + uint32_t entry_size; + uint32_t entry_capture_size; + struct { + uint8_t entry_capture_mask; + uint8_t entry_code; + uint8_t driver_code; + uint8_t driver_flags; + } d_ctrl; +}; + +/* Read CRB entry header */ +struct qla82xx_minidump_entry_crb { + struct qla82xx_minidump_entry_hdr h; + uint32_t addr; + struct { + uint8_t addr_stride; + uint8_t state_index_a; + uint16_t poll_timeout; + } crb_strd; + uint32_t data_size; + uint32_t op_count; + + struct { + uint8_t opcode; + uint8_t state_index_v; + uint8_t shl; + uint8_t shr; + } crb_ctrl; + + uint32_t value_1; + uint32_t value_2; + uint32_t value_3; +}; + +struct qla82xx_minidump_entry_cache { + struct qla82xx_minidump_entry_hdr h; + uint32_t tag_reg_addr; + struct { + uint16_t tag_value_stride; + uint16_t init_tag_value; + } addr_ctrl; + uint32_t data_size; + uint32_t op_count; + uint32_t control_addr; + struct { + uint16_t write_value; + uint8_t poll_mask; + uint8_t poll_wait; + } cache_ctrl; + uint32_t read_addr; + struct { + uint8_t read_addr_stride; + uint8_t read_addr_cnt; + uint16_t rsvd_1; + } read_ctrl; +}; + +/* Read OCM */ +struct qla82xx_minidump_entry_rdocm { + struct qla82xx_minidump_entry_hdr h; + uint32_t rsvd_0; + uint32_t rsvd_1; + uint32_t data_size; + uint32_t op_count; + uint32_t rsvd_2; + uint32_t rsvd_3; + uint32_t read_addr; + uint32_t read_addr_stride; +}; + +/* Read Memory */ +struct qla82xx_minidump_entry_rdmem { + struct qla82xx_minidump_entry_hdr h; + uint32_t rsvd[6]; + uint32_t read_addr; + uint32_t read_data_size; +}; + +/* Read ROM */ +struct qla82xx_minidump_entry_rdrom { + struct qla82xx_minidump_entry_hdr h; + uint32_t rsvd[6]; + uint32_t read_addr; + uint32_t read_data_size; +}; + +/* Mux entry */ +struct qla82xx_minidump_entry_mux { + struct qla82xx_minidump_entry_hdr h; + uint32_t select_addr; + uint32_t rsvd_0; + uint32_t data_size; + uint32_t op_count; + uint32_t select_value; + uint32_t select_value_stride; + uint32_t read_addr; + uint32_t rsvd_1; +}; + +/* Queue entry */ +struct qla82xx_minidump_entry_queue { + struct qla82xx_minidump_entry_hdr h; + uint32_t select_addr; + struct { + uint16_t queue_id_stride; + uint16_t rsvd_0; + } q_strd; + uint32_t data_size; + uint32_t op_count; + uint32_t rsvd_1; + uint32_t rsvd_2; + uint32_t read_addr; + struct { + uint8_t read_addr_stride; + uint8_t read_addr_cnt; + uint16_t rsvd_3; + } rd_strd; +}; + +#define QLA82XX_MINIDUMP_OCM0_SIZE (256 * 1024) +#define QLA82XX_MINIDUMP_L1C_SIZE (256 * 1024) +#define QLA82XX_MINIDUMP_L2C_SIZE 1572864 +#define QLA82XX_MINIDUMP_COMMON_STR_SIZE 0 +#define QLA82XX_MINIDUMP_FCOE_STR_SIZE 0 +#define QLA82XX_MINIDUMP_MEM_SIZE 0 +#define QLA82XX_MAX_ENTRY_HDR 4 + +struct qla82xx_minidump { + uint32_t md_ocm0_data[QLA82XX_MINIDUMP_OCM0_SIZE]; + uint32_t md_l1c_data[QLA82XX_MINIDUMP_L1C_SIZE]; + uint32_t md_l2c_data[QLA82XX_MINIDUMP_L2C_SIZE]; + uint32_t md_cs_data[QLA82XX_MINIDUMP_COMMON_STR_SIZE]; + uint32_t md_fcoes_data[QLA82XX_MINIDUMP_FCOE_STR_SIZE]; + uint32_t md_mem_data[QLA82XX_MINIDUMP_MEM_SIZE]; +}; + +#define MBC_DIAGNOSTIC_MINIDUMP_TEMPLATE 0x129 +#define RQST_TMPLT_SIZE 0x0 +#define RQST_TMPLT 0x1 +#define MD_DIRECT_ROM_WINDOW 0x42110030 +#define MD_DIRECT_ROM_READ_BASE 0x42150000 +#define MD_MIU_TEST_AGT_CTRL 0x41000090 +#define MD_MIU_TEST_AGT_ADDR_LO 0x41000094 +#define MD_MIU_TEST_AGT_ADDR_HI 0x41000098 + +static const int MD_MIU_TEST_AGT_RDDATA[] = { 0x410000A8, + 0x410000AC, 0x410000B8, 0x410000BC }; #endif diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c index ee47820c30a..cd15678f9ad 100644 --- a/drivers/scsi/qla4xxx/ql4_os.c +++ b/drivers/scsi/qla4xxx/ql4_os.c @@ -68,12 +68,34 @@ MODULE_PARM_DESC(ql4xmaxqdepth, " Maximum queue depth to report for target devices.\n" "\t\t Default: 32."); +static int ql4xqfulltracking = 1; +module_param(ql4xqfulltracking, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ql4xqfulltracking, + " Enable or disable dynamic tracking and adjustment of\n" + "\t\t scsi device queue depth.\n" + "\t\t 0 - Disable.\n" + "\t\t 1 - Enable. (Default)"); + static int ql4xsess_recovery_tmo = QL4_SESS_RECOVERY_TMO; module_param(ql4xsess_recovery_tmo, int, S_IRUGO); MODULE_PARM_DESC(ql4xsess_recovery_tmo, " Target Session Recovery Timeout.\n" "\t\t Default: 120 sec."); +int ql4xmdcapmask = 0x1F; +module_param(ql4xmdcapmask, int, S_IRUGO); +MODULE_PARM_DESC(ql4xmdcapmask, + " Set the Minidump driver capture mask level.\n" + "\t\t Default is 0x1F.\n" + "\t\t Can be set to 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F"); + +int ql4xenablemd = 1; +module_param(ql4xenablemd, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ql4xenablemd, + " Set to enable minidump.\n" + "\t\t 0 - disable minidump\n" + "\t\t 1 - enable minidump (Default)"); + static int qla4xxx_wait_for_hba_online(struct scsi_qla_host *ha); /* * SCSI host template entry points @@ -140,6 +162,8 @@ static int qla4xxx_slave_configure(struct scsi_device *device); static void qla4xxx_slave_destroy(struct scsi_device *sdev); static umode_t ql4_attr_is_visible(int param_type, int param); static int qla4xxx_host_reset(struct Scsi_Host *shost, int reset_type); +static int qla4xxx_change_queue_depth(struct scsi_device *sdev, int qdepth, + int reason); static struct qla4_8xxx_legacy_intr_set legacy_intr[] = QLA82XX_LEGACY_INTR_CONFIG; @@ -159,6 +183,7 @@ static struct scsi_host_template qla4xxx_driver_template = { .slave_configure = qla4xxx_slave_configure, .slave_alloc = qla4xxx_slave_alloc, .slave_destroy = qla4xxx_slave_destroy, + .change_queue_depth = qla4xxx_change_queue_depth, .this_id = -1, .cmd_per_lun = 3, @@ -1555,19 +1580,53 @@ static void qla4xxx_session_destroy(struct iscsi_cls_session *cls_sess) struct iscsi_session *sess; struct ddb_entry *ddb_entry; struct scsi_qla_host *ha; - unsigned long flags; + unsigned long flags, wtime; + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + uint32_t ddb_state; + int ret; DEBUG2(printk(KERN_INFO "Func: %s\n", __func__)); sess = cls_sess->dd_data; ddb_entry = sess->dd_data; ha = ddb_entry->ha; + fw_ddb_entry = dma_alloc_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + &fw_ddb_entry_dma, GFP_KERNEL); + if (!fw_ddb_entry) { + ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", __func__); + goto destroy_session; + } + + wtime = jiffies + (HZ * LOGOUT_TOV); + do { + ret = qla4xxx_get_fwddb_entry(ha, ddb_entry->fw_ddb_index, + fw_ddb_entry, fw_ddb_entry_dma, + NULL, NULL, &ddb_state, NULL, + NULL, NULL); + if (ret == QLA_ERROR) + goto destroy_session; + + if ((ddb_state == DDB_DS_NO_CONNECTION_ACTIVE) || + (ddb_state == DDB_DS_SESSION_FAILED)) + goto destroy_session; + + schedule_timeout_uninterruptible(HZ); + } while ((time_after(wtime, jiffies))); + +destroy_session: qla4xxx_clear_ddb_entry(ha, ddb_entry->fw_ddb_index); spin_lock_irqsave(&ha->hardware_lock, flags); qla4xxx_free_ddb(ha, ddb_entry); spin_unlock_irqrestore(&ha->hardware_lock, flags); + iscsi_session_teardown(cls_sess); + + if (fw_ddb_entry) + dma_free_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + fw_ddb_entry, fw_ddb_entry_dma); } static struct iscsi_cls_conn * @@ -2220,6 +2279,9 @@ static void qla4xxx_mem_free(struct scsi_qla_host *ha) dma_free_coherent(&ha->pdev->dev, ha->queues_len, ha->queues, ha->queues_dma); + if (ha->fw_dump) + vfree(ha->fw_dump); + ha->queues_len = 0; ha->queues = NULL; ha->queues_dma = 0; @@ -2229,6 +2291,8 @@ static void qla4xxx_mem_free(struct scsi_qla_host *ha) ha->response_dma = 0; ha->shadow_regs = NULL; ha->shadow_regs_dma = 0; + ha->fw_dump = NULL; + ha->fw_dump_size = 0; /* Free srb pool. */ if (ha->srb_mempool) @@ -5023,6 +5087,8 @@ static int __devinit qla4xxx_probe_adapter(struct pci_dev *pdev, set_bit(AF_INIT_DONE, &ha->flags); + qla4_8xxx_alloc_sysfs_attr(ha); + printk(KERN_INFO " QLogic iSCSI HBA Driver version: %s\n" " QLogic ISP%04x @ %s, host#=%ld, fw=%02d.%02d.%02d.%02d\n", @@ -5149,6 +5215,7 @@ static void __devexit qla4xxx_remove_adapter(struct pci_dev *pdev) iscsi_boot_destroy_kset(ha->boot_kset); qla4xxx_destroy_fw_ddb_session(ha); + qla4_8xxx_free_sysfs_attr(ha); scsi_remove_host(ha->host); @@ -5217,6 +5284,15 @@ static void qla4xxx_slave_destroy(struct scsi_device *sdev) scsi_deactivate_tcq(sdev, 1); } +static int qla4xxx_change_queue_depth(struct scsi_device *sdev, int qdepth, + int reason) +{ + if (!ql4xqfulltracking) + return -EOPNOTSUPP; + + return iscsi_change_queue_depth(sdev, qdepth, reason); +} + /** * qla4xxx_del_from_active_array - returns an active srb * @ha: Pointer to host adapter structure. diff --git a/drivers/scsi/qla4xxx/ql4_version.h b/drivers/scsi/qla4xxx/ql4_version.h index 97b30c108e3..cc1cc3518b8 100644 --- a/drivers/scsi/qla4xxx/ql4_version.h +++ b/drivers/scsi/qla4xxx/ql4_version.h @@ -5,4 +5,4 @@ * See LICENSE.qla4xxx for copyright and licensing details. */ -#define QLA4XXX_DRIVER_VERSION "5.02.00-k16" +#define QLA4XXX_DRIVER_VERSION "5.02.00-k17" diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 62ddfd31d4c..6dfb9785d34 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1378,16 +1378,19 @@ static int scsi_lld_busy(struct request_queue *q) { struct scsi_device *sdev = q->queuedata; struct Scsi_Host *shost; - struct scsi_target *starget; if (!sdev) return 0; shost = sdev->host; - starget = scsi_target(sdev); - if (scsi_host_in_recovery(shost) || scsi_host_is_busy(shost) || - scsi_target_is_busy(starget) || scsi_device_is_busy(sdev)) + /* + * Ignore host/starget busy state. + * Since block layer does not have a concept of fairness across + * multiple queues, congestion of host/starget needs to be handled + * in SCSI layer. + */ + if (scsi_host_in_recovery(shost) || scsi_device_is_busy(sdev)) return 1; return 0; diff --git a/drivers/scsi/scsi_pm.c b/drivers/scsi/scsi_pm.c index f661a41fa4c..d4201ded3b2 100644 --- a/drivers/scsi/scsi_pm.c +++ b/drivers/scsi/scsi_pm.c @@ -24,8 +24,11 @@ static int scsi_dev_type_suspend(struct device *dev, pm_message_t msg) err = scsi_device_quiesce(to_scsi_device(dev)); if (err == 0) { drv = dev->driver; - if (drv && drv->suspend) + if (drv && drv->suspend) { err = drv->suspend(dev, msg); + if (err) + scsi_device_resume(to_scsi_device(dev)); + } } dev_dbg(dev, "scsi suspend: %d\n", err); return err; diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 01b03744f1f..2e5fe584aad 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -147,7 +147,7 @@ int scsi_complete_async_scans(void) do { if (list_empty(&scanning_hosts)) - return 0; + goto out; /* If we can't get memory immediately, that's OK. Just * sleep a little. Even if we never get memory, the async * scans will finish eventually. @@ -179,8 +179,11 @@ int scsi_complete_async_scans(void) } done: spin_unlock(&async_scan_lock); - kfree(data); + + out: + async_synchronize_full_domain(&scsi_sd_probe_domain); + return 0; } diff --git a/drivers/scsi/scsi_wait_scan.c b/drivers/scsi/scsi_wait_scan.c index 74708fcaf82..ae781487461 100644 --- a/drivers/scsi/scsi_wait_scan.c +++ b/drivers/scsi/scsi_wait_scan.c @@ -12,7 +12,7 @@ #include <linux/module.h> #include <linux/device.h> -#include <scsi/scsi_scan.h> +#include "scsi_priv.h" static int __init wait_scan_init(void) { diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 4e010b72781..6a4fd00117c 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -1836,7 +1836,7 @@ ufshcd_probe(struct pci_dev *pdev, const struct pci_device_id *id) err = pci_request_regions(pdev, UFSHCD); if (err < 0) { dev_err(&pdev->dev, "request regions failed\n"); - goto out_disable; + goto out_host_put; } hba->mmio_base = pci_ioremap_bar(pdev, 0); @@ -1925,8 +1925,9 @@ out_iounmap: iounmap(hba->mmio_base); out_release_regions: pci_release_regions(pdev); -out_disable: +out_host_put: scsi_host_put(host); +out_disable: pci_clear_master(pdev); pci_disable_device(pdev); out_error: diff --git a/drivers/tty/serial/lantiq.c b/drivers/tty/serial/lantiq.c index 96c1cacc736..02da071fe1e 100644 --- a/drivers/tty/serial/lantiq.c +++ b/drivers/tty/serial/lantiq.c @@ -31,16 +31,19 @@ #include <linux/tty_flip.h> #include <linux/serial_core.h> #include <linux/serial.h> -#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> #include <linux/io.h> #include <linux/clk.h> +#include <linux/gpio.h> #include <lantiq_soc.h> #define PORT_LTQ_ASC 111 #define MAXPORTS 2 #define UART_DUMMY_UER_RX 1 -#define DRVNAME "ltq_asc" +#define DRVNAME "lantiq,asc" #ifdef __BIG_ENDIAN #define LTQ_ASC_TBUF (0x0020 + 3) #define LTQ_ASC_RBUF (0x0024 + 3) @@ -114,6 +117,9 @@ static DEFINE_SPINLOCK(ltq_asc_lock); struct ltq_uart_port { struct uart_port port; + /* clock used to derive divider */ + struct clk *fpiclk; + /* clock gating of the ASC core */ struct clk *clk; unsigned int tx_irq; unsigned int rx_irq; @@ -316,7 +322,9 @@ lqasc_startup(struct uart_port *port) struct ltq_uart_port *ltq_port = to_ltq_uart_port(port); int retval; - port->uartclk = clk_get_rate(ltq_port->clk); + if (ltq_port->clk) + clk_enable(ltq_port->clk); + port->uartclk = clk_get_rate(ltq_port->fpiclk); ltq_w32_mask(ASCCLC_DISS | ASCCLC_RMCMASK, (1 << ASCCLC_RMCOFFSET), port->membase + LTQ_ASC_CLC); @@ -382,6 +390,8 @@ lqasc_shutdown(struct uart_port *port) port->membase + LTQ_ASC_RXFCON); ltq_w32_mask(ASCTXFCON_TXFEN, ASCTXFCON_TXFFLU, port->membase + LTQ_ASC_TXFCON); + if (ltq_port->clk) + clk_disable(ltq_port->clk); } static void @@ -630,7 +640,7 @@ lqasc_console_setup(struct console *co, char *options) port = <q_port->port; - port->uartclk = clk_get_rate(ltq_port->clk); + port->uartclk = clk_get_rate(ltq_port->fpiclk); if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); @@ -668,37 +678,32 @@ static struct uart_driver lqasc_reg = { static int __init lqasc_probe(struct platform_device *pdev) { + struct device_node *node = pdev->dev.of_node; struct ltq_uart_port *ltq_port; struct uart_port *port; - struct resource *mmres, *irqres; - int tx_irq, rx_irq, err_irq; - struct clk *clk; + struct resource *mmres, irqres[3]; + int line = 0; int ret; mmres = platform_get_resource(pdev, IORESOURCE_MEM, 0); - irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!mmres || !irqres) + ret = of_irq_to_resource_table(node, irqres, 3); + if (!mmres || (ret != 3)) { + dev_err(&pdev->dev, + "failed to get memory/irq for serial port\n"); return -ENODEV; + } - if (pdev->id >= MAXPORTS) - return -EBUSY; + /* check if this is the console port */ + if (mmres->start != CPHYSADDR(LTQ_EARLY_ASC)) + line = 1; - if (lqasc_port[pdev->id] != NULL) + if (lqasc_port[line]) { + dev_err(&pdev->dev, "port %d already allocated\n", line); return -EBUSY; - - clk = clk_get(&pdev->dev, "fpi"); - if (IS_ERR(clk)) { - pr_err("failed to get fpi clk\n"); - return -ENOENT; } - tx_irq = platform_get_irq_byname(pdev, "tx"); - rx_irq = platform_get_irq_byname(pdev, "rx"); - err_irq = platform_get_irq_byname(pdev, "err"); - if ((tx_irq < 0) | (rx_irq < 0) | (err_irq < 0)) - return -ENODEV; - - ltq_port = kzalloc(sizeof(struct ltq_uart_port), GFP_KERNEL); + ltq_port = devm_kzalloc(&pdev->dev, sizeof(struct ltq_uart_port), + GFP_KERNEL); if (!ltq_port) return -ENOMEM; @@ -709,19 +714,26 @@ lqasc_probe(struct platform_device *pdev) port->ops = &lqasc_pops; port->fifosize = 16; port->type = PORT_LTQ_ASC, - port->line = pdev->id; + port->line = line; port->dev = &pdev->dev; - - port->irq = tx_irq; /* unused, just to be backward-compatibe */ + /* unused, just to be backward-compatible */ + port->irq = irqres[0].start; port->mapbase = mmres->start; - ltq_port->clk = clk; + ltq_port->fpiclk = clk_get_fpi(); + if (IS_ERR(ltq_port->fpiclk)) { + pr_err("failed to get fpi clk\n"); + return -ENOENT; + } - ltq_port->tx_irq = tx_irq; - ltq_port->rx_irq = rx_irq; - ltq_port->err_irq = err_irq; + /* not all asc ports have clock gates, lets ignore the return code */ + ltq_port->clk = clk_get(&pdev->dev, NULL); - lqasc_port[pdev->id] = ltq_port; + ltq_port->tx_irq = irqres[0].start; + ltq_port->rx_irq = irqres[1].start; + ltq_port->err_irq = irqres[2].start; + + lqasc_port[line] = ltq_port; platform_set_drvdata(pdev, ltq_port); ret = uart_add_one_port(&lqasc_reg, port); @@ -729,10 +741,17 @@ lqasc_probe(struct platform_device *pdev) return ret; } +static const struct of_device_id ltq_asc_match[] = { + { .compatible = DRVNAME }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_asc_match); + static struct platform_driver lqasc_driver = { .driver = { .name = DRVNAME, .owner = THIS_MODULE, + .of_match_table = ltq_asc_match, }, }; diff --git a/drivers/tty/serial/sb1250-duart.c b/drivers/tty/serial/sb1250-duart.c index 0be8a2f00d0..f76b1688c5c 100644 --- a/drivers/tty/serial/sb1250-duart.c +++ b/drivers/tty/serial/sb1250-duart.c @@ -31,6 +31,7 @@ #include <linux/interrupt.h> #include <linux/ioport.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/major.h> #include <linux/serial.h> #include <linux/serial_core.h> diff --git a/drivers/tty/serial/zs.c b/drivers/tty/serial/zs.c index 4001eee6c08..92c00b24d0d 100644 --- a/drivers/tty/serial/zs.c +++ b/drivers/tty/serial/zs.c @@ -57,6 +57,7 @@ #include <linux/ioport.h> #include <linux/irqflags.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/major.h> #include <linux/serial.h> #include <linux/serial_core.h> diff --git a/drivers/tty/tty_mutex.c b/drivers/tty/tty_mutex.c index 69adc80c98c..67feac9e6eb 100644 --- a/drivers/tty/tty_mutex.c +++ b/drivers/tty/tty_mutex.c @@ -6,11 +6,17 @@ /* Legacy tty mutex glue */ +enum { + TTY_MUTEX_NORMAL, + TTY_MUTEX_NESTED, +}; + /* * Getting the big tty mutex. */ -void __lockfunc tty_lock(struct tty_struct *tty) +static void __lockfunc tty_lock_nested(struct tty_struct *tty, + unsigned int subclass) { if (tty->magic != TTY_MAGIC) { printk(KERN_ERR "L Bad %p\n", tty); @@ -18,7 +24,12 @@ void __lockfunc tty_lock(struct tty_struct *tty) return; } tty_kref_get(tty); - mutex_lock(&tty->legacy_mutex); + mutex_lock_nested(&tty->legacy_mutex, subclass); +} + +void __lockfunc tty_lock(struct tty_struct *tty) +{ + return tty_lock_nested(tty, TTY_MUTEX_NORMAL); } EXPORT_SYMBOL(tty_lock); @@ -43,11 +54,11 @@ void __lockfunc tty_lock_pair(struct tty_struct *tty, { if (tty < tty2) { tty_lock(tty); - tty_lock(tty2); + tty_lock_nested(tty2, TTY_MUTEX_NESTED); } else { if (tty2 && tty2 != tty) tty_lock(tty2); - tty_lock(tty); + tty_lock_nested(tty, TTY_MUTEX_NESTED); } } EXPORT_SYMBOL(tty_lock_pair); diff --git a/drivers/video/matrox/matroxfb_maven.c b/drivers/video/matrox/matroxfb_maven.c index 31b8f67477b..217678e0b98 100644 --- a/drivers/video/matrox/matroxfb_maven.c +++ b/drivers/video/matrox/matroxfb_maven.c @@ -1243,6 +1243,7 @@ static int maven_probe(struct i2c_client *client, if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_WORD_DATA | I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING)) goto ERROR0; if (!(data = kzalloc(sizeof(*data), GFP_KERNEL))) { diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index d92d7488be1..fe819b76de5 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -64,6 +64,18 @@ config SOFT_WATCHDOG To compile this driver as a module, choose M here: the module will be called softdog. +config DA9052_WATCHDOG + tristate "Dialog DA9052 Watchdog" + depends on PMIC_DA9052 + select WATCHDOG_CORE + help + Support for the watchdog in the DA9052 PMIC. Watchdog trigger + cause system reset. + + Say Y here to include support for the DA9052 watchdog. + Alternatively say M to compile the driver as a module, + which will be called da9052_wdt. + config WM831X_WATCHDOG tristate "WM831x watchdog" depends on MFD_WM831X @@ -87,6 +99,7 @@ config WM8350_WATCHDOG config ARM_SP805_WATCHDOG tristate "ARM SP805 Watchdog" depends on ARM_AMBA + select WATCHDOG_CORE help ARM Primecell SP805 Watchdog timer. This will reboot your system when the timeout is reached. diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 442bfbe0882..572b39bed06 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -163,6 +163,7 @@ obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o obj-$(CONFIG_XEN_WDT) += xen_wdt.o # Architecture Independent +obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c new file mode 100644 index 00000000000..3f75129eb0a --- /dev/null +++ b/drivers/watchdog/da9052_wdt.c @@ -0,0 +1,251 @@ +/* + * System monitoring driver for DA9052 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: Anthony Olech <Anthony.Olech@diasemi.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/time.h> +#include <linux/watchdog.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/delay.h> + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/da9052.h> + +#define DA9052_DEF_TIMEOUT 4 +#define DA9052_TWDMIN 256 + +struct da9052_wdt_data { + struct watchdog_device wdt; + struct da9052 *da9052; + struct kref kref; + unsigned long jpast; +}; + +static const struct { + u8 reg_val; + int time; /* Seconds */ +} da9052_wdt_maps[] = { + { 1, 2 }, + { 2, 4 }, + { 3, 8 }, + { 4, 16 }, + { 5, 32 }, + { 5, 33 }, /* Actual time 32.768s so included both 32s and 33s */ + { 6, 65 }, + { 6, 66 }, /* Actual time 65.536s so include both, 65s and 66s */ + { 7, 131 }, +}; + + +static void da9052_wdt_release_resources(struct kref *r) +{ + struct da9052_wdt_data *driver_data = + container_of(r, struct da9052_wdt_data, kref); + + kfree(driver_data); +} + +static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int timeout) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + struct da9052 *da9052 = driver_data->da9052; + int ret, i; + + /* + * Disable the Watchdog timer before setting + * new time out. + */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, 0); + if (ret < 0) { + dev_err(da9052->dev, "Failed to disable watchdog bit, %d\n", + ret); + return ret; + } + if (timeout) { + /* + * To change the timeout, da9052 needs to + * be disabled for at least 150 us. + */ + udelay(150); + + /* Set the desired timeout */ + for (i = 0; i < ARRAY_SIZE(da9052_wdt_maps); i++) + if (da9052_wdt_maps[i].time == timeout) + break; + + if (i == ARRAY_SIZE(da9052_wdt_maps)) + ret = -EINVAL; + else + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, + da9052_wdt_maps[i].reg_val); + if (ret < 0) { + dev_err(da9052->dev, + "Failed to update timescale bit, %d\n", ret); + return ret; + } + + wdt_dev->timeout = timeout; + driver_data->jpast = jiffies; + } + + return 0; +} + +static void da9052_wdt_ref(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_get(&driver_data->kref); +} + +static void da9052_wdt_unref(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_put(&driver_data->kref, da9052_wdt_release_resources); +} + +static int da9052_wdt_start(struct watchdog_device *wdt_dev) +{ + return da9052_wdt_set_timeout(wdt_dev, wdt_dev->timeout); +} + +static int da9052_wdt_stop(struct watchdog_device *wdt_dev) +{ + return da9052_wdt_set_timeout(wdt_dev, 0); +} + +static int da9052_wdt_ping(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + struct da9052 *da9052 = driver_data->da9052; + unsigned long msec, jnow = jiffies; + int ret; + + /* + * We have a minimum time for watchdog window called TWDMIN. A write + * to the watchdog before this elapsed time should cause an error. + */ + msec = (jnow - driver_data->jpast) * 1000/HZ; + if (msec < DA9052_TWDMIN) + mdelay(msec); + + /* Reset the watchdog timer */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_WATCHDOG, 1 << 7); + if (ret < 0) + goto err_strobe; + + /* + * FIXME: Reset the watchdog core, in general PMIC + * is supposed to do this + */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_WATCHDOG, 0 << 7); +err_strobe: + return ret; +} + +static struct watchdog_info da9052_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "DA9052 Watchdog", +}; + +static const struct watchdog_ops da9052_wdt_ops = { + .owner = THIS_MODULE, + .start = da9052_wdt_start, + .stop = da9052_wdt_stop, + .ping = da9052_wdt_ping, + .set_timeout = da9052_wdt_set_timeout, + .ref = da9052_wdt_ref, + .unref = da9052_wdt_unref, +}; + + +static int __devinit da9052_wdt_probe(struct platform_device *pdev) +{ + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + struct da9052_wdt_data *driver_data; + struct watchdog_device *da9052_wdt; + int ret; + + driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), + GFP_KERNEL); + if (!driver_data) { + dev_err(da9052->dev, "Unable to alloacate watchdog device\n"); + ret = -ENOMEM; + goto err; + } + driver_data->da9052 = da9052; + + da9052_wdt = &driver_data->wdt; + + da9052_wdt->timeout = DA9052_DEF_TIMEOUT; + da9052_wdt->info = &da9052_wdt_info; + da9052_wdt->ops = &da9052_wdt_ops; + watchdog_set_drvdata(da9052_wdt, driver_data); + + kref_init(&driver_data->kref); + + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, 0); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to disable watchdog bits, %d\n", + ret); + goto err; + } + + ret = watchdog_register_device(&driver_data->wdt); + if (ret != 0) { + dev_err(da9052->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err; + } + + dev_set_drvdata(&pdev->dev, driver_data); +err: + return ret; +} + +static int __devexit da9052_wdt_remove(struct platform_device *pdev) +{ + struct da9052_wdt_data *driver_data = dev_get_drvdata(&pdev->dev); + + watchdog_unregister_device(&driver_data->wdt); + kref_put(&driver_data->kref, da9052_wdt_release_resources); + + return 0; +} + +static struct platform_driver da9052_wdt_driver = { + .probe = da9052_wdt_probe, + .remove = __devexit_p(da9052_wdt_remove), + .driver = { + .name = "da9052-watchdog", + }, +}; + +module_platform_driver(da9052_wdt_driver); + +MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>"); +MODULE_DESCRIPTION("DA9052 SM Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-watchdog"); diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index 741528b032e..bc47e9012f3 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -575,7 +575,7 @@ static int __devinit iTCO_wdt_probe(struct platform_device *dev) if (!request_region(iTCO_wdt_private.smi_res->start, resource_size(iTCO_wdt_private.smi_res), dev->name)) { pr_err("I/O address 0x%04llx already in use, device disabled\n", - SMI_EN); + (u64)SMI_EN); ret = -EBUSY; goto unmap_gcs; } @@ -592,13 +592,13 @@ static int __devinit iTCO_wdt_probe(struct platform_device *dev) if (!request_region(iTCO_wdt_private.tco_res->start, resource_size(iTCO_wdt_private.tco_res), dev->name)) { pr_err("I/O address 0x%04llx already in use, device disabled\n", - TCOBASE); + (u64)TCOBASE); ret = -EBUSY; goto unreg_smi; } pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n", - ich_info->name, ich_info->iTCO_version, TCOBASE); + ich_info->name, ich_info->iTCO_version, (u64)TCOBASE); /* Clear out the (probably old) status */ outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */ diff --git a/drivers/watchdog/lantiq_wdt.c b/drivers/watchdog/lantiq_wdt.c index a9593a3a32a..2e74c3a8ee5 100644 --- a/drivers/watchdog/lantiq_wdt.c +++ b/drivers/watchdog/lantiq_wdt.c @@ -13,14 +13,15 @@ #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> -#include <linux/platform_device.h> +#include <linux/of_platform.h> #include <linux/uaccess.h> #include <linux/clk.h> #include <linux/io.h> -#include <lantiq.h> +#include <lantiq_soc.h> -/* Section 3.4 of the datasheet +/* + * Section 3.4 of the datasheet * The password sequence protects the WDT control register from unintended * write actions, which might cause malfunction of the WDT. * @@ -70,7 +71,8 @@ ltq_wdt_disable(void) { /* write the first password magic */ ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR); - /* write the second password magic with no config + /* + * write the second password magic with no config * this turns the watchdog off */ ltq_w32(LTQ_WDT_PW2, ltq_wdt_membase + LTQ_WDT_CR); @@ -184,7 +186,7 @@ static struct miscdevice ltq_wdt_miscdev = { .fops = <q_wdt_fops, }; -static int __init +static int __devinit ltq_wdt_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -194,28 +196,27 @@ ltq_wdt_probe(struct platform_device *pdev) dev_err(&pdev->dev, "cannot obtain I/O memory region"); return -ENOENT; } - res = devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), dev_name(&pdev->dev)); - if (!res) { - dev_err(&pdev->dev, "cannot request I/O memory region"); - return -EBUSY; - } - ltq_wdt_membase = devm_ioremap_nocache(&pdev->dev, res->start, - resource_size(res)); + + ltq_wdt_membase = devm_request_and_ioremap(&pdev->dev, res); if (!ltq_wdt_membase) { dev_err(&pdev->dev, "cannot remap I/O memory region\n"); return -ENOMEM; } /* we do not need to enable the clock as it is always running */ - clk = clk_get(&pdev->dev, "io"); - WARN_ON(!clk); + clk = clk_get_io(); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return -ENOENT; + } ltq_io_region_clk_rate = clk_get_rate(clk); clk_put(clk); + /* find out if the watchdog caused the last reboot */ if (ltq_reset_cause() == LTQ_RST_CAUSE_WDTRST) ltq_wdt_bootstatus = WDIOF_CARDRESET; + dev_info(&pdev->dev, "Init done\n"); return misc_register(<q_wdt_miscdev); } @@ -227,33 +228,26 @@ ltq_wdt_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ltq_wdt_match[] = { + { .compatible = "lantiq,wdt" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_wdt_match); static struct platform_driver ltq_wdt_driver = { + .probe = ltq_wdt_probe, .remove = __devexit_p(ltq_wdt_remove), .driver = { - .name = "ltq_wdt", + .name = "wdt", .owner = THIS_MODULE, + .of_match_table = ltq_wdt_match, }, }; -static int __init -init_ltq_wdt(void) -{ - return platform_driver_probe(<q_wdt_driver, ltq_wdt_probe); -} - -static void __exit -exit_ltq_wdt(void) -{ - return platform_driver_unregister(<q_wdt_driver); -} - -module_init(init_ltq_wdt); -module_exit(exit_ltq_wdt); +module_platform_driver(ltq_wdt_driver); module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); - MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); MODULE_DESCRIPTION("Lantiq SoC Watchdog"); MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index bbb170e5005..afcd1367654 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -16,20 +16,17 @@ #include <linux/amba/bus.h> #include <linux/bitops.h> #include <linux/clk.h> -#include <linux/fs.h> #include <linux/init.h> #include <linux/io.h> #include <linux/ioport.h> #include <linux/kernel.h> #include <linux/math64.h> -#include <linux/miscdevice.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/pm.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/types.h> -#include <linux/uaccess.h> #include <linux/watchdog.h> /* default timeout in seconds */ @@ -56,6 +53,7 @@ /** * struct sp805_wdt: sp805 wdt device structure + * @wdd: instance of struct watchdog_device * @lock: spin lock protecting dev structure and io access * @base: base address of wdt * @clk: clock structure of wdt @@ -65,24 +63,24 @@ * @timeout: current programmed timeout */ struct sp805_wdt { + struct watchdog_device wdd; spinlock_t lock; void __iomem *base; struct clk *clk; struct amba_device *adev; - unsigned long status; - #define WDT_BUSY 0 - #define WDT_CAN_BE_CLOSED 1 unsigned int load_val; unsigned int timeout; }; -/* local variables */ -static struct sp805_wdt *wdt; static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); /* This routine finds load value that will reset system in required timout */ -static void wdt_setload(unsigned int timeout) +static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout) { + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); u64 load, rate; rate = clk_get_rate(wdt->clk); @@ -103,11 +101,14 @@ static void wdt_setload(unsigned int timeout) /* roundup timeout to closest positive integer value */ wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); spin_unlock(&wdt->lock); + + return 0; } /* returns number of seconds left for reset to occur */ -static u32 wdt_timeleft(void) +static unsigned int wdt_timeleft(struct watchdog_device *wdd) { + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); u64 load, rate; rate = clk_get_rate(wdt->clk); @@ -123,166 +124,96 @@ static u32 wdt_timeleft(void) return div_u64(load, rate); } -/* enables watchdog timers reset */ -static void wdt_enable(void) +static int wdt_config(struct watchdog_device *wdd, bool ping) { - spin_lock(&wdt->lock); + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); + int ret; - writel_relaxed(UNLOCK, wdt->base + WDTLOCK); - writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); - writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); - writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); - writel_relaxed(LOCK, wdt->base + WDTLOCK); + if (!ping) { + ret = clk_prepare(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock prepare fail"); + return ret; + } - /* Flush posted writes. */ - readl_relaxed(wdt->base + WDTLOCK); - spin_unlock(&wdt->lock); -} + ret = clk_enable(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock enable fail"); + clk_unprepare(wdt->clk); + return ret; + } + } -/* disables watchdog timers reset */ -static void wdt_disable(void) -{ spin_lock(&wdt->lock); writel_relaxed(UNLOCK, wdt->base + WDTLOCK); - writel_relaxed(0, wdt->base + WDTCONTROL); + writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); + + if (!ping) { + writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); + writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + + WDTCONTROL); + } + writel_relaxed(LOCK, wdt->base + WDTLOCK); /* Flush posted writes. */ readl_relaxed(wdt->base + WDTLOCK); spin_unlock(&wdt->lock); + + return 0; } -static ssize_t sp805_wdt_write(struct file *file, const char *data, - size_t len, loff_t *ppos) +static int wdt_ping(struct watchdog_device *wdd) { - if (len) { - if (!nowayout) { - size_t i; - - clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - /* Check for Magic Close character */ - if (c == 'V') { - set_bit(WDT_CAN_BE_CLOSED, - &wdt->status); - break; - } - } - } - wdt_enable(); - } - return len; + return wdt_config(wdd, true); } -static const struct watchdog_info ident = { - .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .identity = MODULE_NAME, -}; - -static long sp805_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +/* enables watchdog timers reset */ +static int wdt_enable(struct watchdog_device *wdd) { - int ret = -ENOTTY; - unsigned int timeout; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user((struct watchdog_info *)arg, &ident, - sizeof(ident)) ? -EFAULT : 0; - break; - - case WDIOC_GETSTATUS: - ret = put_user(0, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(timeout, (unsigned int *)arg); - if (ret) - break; - - wdt_setload(timeout); - - wdt_enable(); - /* Fall through */ - - case WDIOC_GETTIMEOUT: - ret = put_user(wdt->timeout, (unsigned int *)arg); - break; - case WDIOC_GETTIMELEFT: - ret = put_user(wdt_timeleft(), (unsigned int *)arg); - break; - } - return ret; + return wdt_config(wdd, false); } -static int sp805_wdt_open(struct inode *inode, struct file *file) +/* disables watchdog timers reset */ +static int wdt_disable(struct watchdog_device *wdd) { - int ret = 0; - - if (test_and_set_bit(WDT_BUSY, &wdt->status)) - return -EBUSY; - - ret = clk_enable(wdt->clk); - if (ret) { - dev_err(&wdt->adev->dev, "clock enable fail"); - goto err; - } - - wdt_enable(); + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); - /* can not be closed, once enabled */ - clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); - return nonseekable_open(inode, file); + spin_lock(&wdt->lock); -err: - clear_bit(WDT_BUSY, &wdt->status); - return ret; -} + writel_relaxed(UNLOCK, wdt->base + WDTLOCK); + writel_relaxed(0, wdt->base + WDTCONTROL); + writel_relaxed(LOCK, wdt->base + WDTLOCK); -static int sp805_wdt_release(struct inode *inode, struct file *file) -{ - if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) { - clear_bit(WDT_BUSY, &wdt->status); - dev_warn(&wdt->adev->dev, "Device closed unexpectedly\n"); - return 0; - } + /* Flush posted writes. */ + readl_relaxed(wdt->base + WDTLOCK); + spin_unlock(&wdt->lock); - wdt_disable(); clk_disable(wdt->clk); - clear_bit(WDT_BUSY, &wdt->status); + clk_unprepare(wdt->clk); return 0; } -static const struct file_operations sp805_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sp805_wdt_write, - .unlocked_ioctl = sp805_wdt_ioctl, - .open = sp805_wdt_open, - .release = sp805_wdt_release, +static const struct watchdog_info wdt_info = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = MODULE_NAME, }; -static struct miscdevice sp805_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sp805_wdt_fops, +static const struct watchdog_ops wdt_ops = { + .owner = THIS_MODULE, + .start = wdt_enable, + .stop = wdt_disable, + .ping = wdt_ping, + .set_timeout = wdt_setload, + .get_timeleft = wdt_timeleft, }; static int __devinit sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) { + struct sp805_wdt *wdt; int ret = 0; if (!devm_request_mem_region(&adev->dev, adev->res.start, @@ -315,19 +246,26 @@ sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) } wdt->adev = adev; + wdt->wdd.info = &wdt_info; + wdt->wdd.ops = &wdt_ops; + spin_lock_init(&wdt->lock); - wdt_setload(DEFAULT_TIMEOUT); + watchdog_set_nowayout(&wdt->wdd, nowayout); + watchdog_set_drvdata(&wdt->wdd, wdt); + wdt_setload(&wdt->wdd, DEFAULT_TIMEOUT); - ret = misc_register(&sp805_wdt_miscdev); - if (ret < 0) { - dev_warn(&adev->dev, "cannot register misc device\n"); - goto err_misc_register; + ret = watchdog_register_device(&wdt->wdd); + if (ret) { + dev_err(&adev->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err_register; } + amba_set_drvdata(adev, wdt); dev_info(&adev->dev, "registration successful\n"); return 0; -err_misc_register: +err_register: clk_put(wdt->clk); err: dev_err(&adev->dev, "Probe Failed!!!\n"); @@ -336,7 +274,11 @@ err: static int __devexit sp805_wdt_remove(struct amba_device *adev) { - misc_deregister(&sp805_wdt_miscdev); + struct sp805_wdt *wdt = amba_get_drvdata(adev); + + watchdog_unregister_device(&wdt->wdd); + amba_set_drvdata(adev, NULL); + watchdog_set_drvdata(&wdt->wdd, NULL); clk_put(wdt->clk); return 0; @@ -345,28 +287,22 @@ static int __devexit sp805_wdt_remove(struct amba_device *adev) #ifdef CONFIG_PM static int sp805_wdt_suspend(struct device *dev) { - if (test_bit(WDT_BUSY, &wdt->status)) { - wdt_disable(); - clk_disable(wdt->clk); - } + struct sp805_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + return wdt_disable(&wdt->wdd); return 0; } static int sp805_wdt_resume(struct device *dev) { - int ret = 0; + struct sp805_wdt *wdt = dev_get_drvdata(dev); - if (test_bit(WDT_BUSY, &wdt->status)) { - ret = clk_enable(wdt->clk); - if (ret) { - dev_err(dev, "clock enable fail"); - return ret; - } - wdt_enable(); - } + if (watchdog_active(&wdt->wdd)) + return wdt_enable(&wdt->wdd); - return ret; + return 0; } #endif /* CONFIG_PM */ @@ -395,11 +331,6 @@ static struct amba_driver sp805_wdt_driver = { module_amba_driver(sp805_wdt_driver); -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, - "Set to 1 to keep watchdog running after device release"); - MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>"); MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c index 5603e31afda..aa50da3ccfe 100644 --- a/drivers/watchdog/via_wdt.c +++ b/drivers/watchdog/via_wdt.c @@ -91,7 +91,7 @@ static inline void wdt_reset(void) static void wdt_timer_tick(unsigned long data) { if (time_before(jiffies, next_heartbeat) || - (!test_bit(WDOG_ACTIVE, &wdt_dev.status))) { + (!watchdog_active(&wdt_dev))) { wdt_reset(); mod_timer(&timer, jiffies + WDT_HEARTBEAT); } else diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 14d768bfa26..6aa46a90ff0 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -34,8 +34,13 @@ #include <linux/kernel.h> /* For printk/panic/... */ #include <linux/watchdog.h> /* For watchdog specific items */ #include <linux/init.h> /* For __init/__exit/... */ +#include <linux/idr.h> /* For ida_* macros */ +#include <linux/err.h> /* For IS_ERR macros */ -#include "watchdog_dev.h" /* For watchdog_dev_register/... */ +#include "watchdog_core.h" /* For watchdog_dev_register/... */ + +static DEFINE_IDA(watchdog_ida); +static struct class *watchdog_class; /** * watchdog_register_device() - register a watchdog device @@ -49,7 +54,7 @@ */ int watchdog_register_device(struct watchdog_device *wdd) { - int ret; + int ret, id, devno; if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL) return -EINVAL; @@ -74,10 +79,38 @@ int watchdog_register_device(struct watchdog_device *wdd) * corrupted in a later stage then we expect a kernel panic! */ - /* We only support 1 watchdog device via the /dev/watchdog interface */ + mutex_init(&wdd->lock); + id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL); + if (id < 0) + return id; + wdd->id = id; + ret = watchdog_dev_register(wdd); if (ret) { - pr_err("error registering /dev/watchdog (err=%d)\n", ret); + ida_simple_remove(&watchdog_ida, id); + if (!(id == 0 && ret == -EBUSY)) + return ret; + + /* Retry in case a legacy watchdog module exists */ + id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL); + if (id < 0) + return id; + wdd->id = id; + + ret = watchdog_dev_register(wdd); + if (ret) { + ida_simple_remove(&watchdog_ida, id); + return ret; + } + } + + devno = wdd->cdev.dev; + wdd->dev = device_create(watchdog_class, wdd->parent, devno, + NULL, "watchdog%d", wdd->id); + if (IS_ERR(wdd->dev)) { + watchdog_dev_unregister(wdd); + ida_simple_remove(&watchdog_ida, id); + ret = PTR_ERR(wdd->dev); return ret; } @@ -95,6 +128,7 @@ EXPORT_SYMBOL_GPL(watchdog_register_device); void watchdog_unregister_device(struct watchdog_device *wdd) { int ret; + int devno = wdd->cdev.dev; if (wdd == NULL) return; @@ -102,9 +136,41 @@ void watchdog_unregister_device(struct watchdog_device *wdd) ret = watchdog_dev_unregister(wdd); if (ret) pr_err("error unregistering /dev/watchdog (err=%d)\n", ret); + device_destroy(watchdog_class, devno); + ida_simple_remove(&watchdog_ida, wdd->id); + wdd->dev = NULL; } EXPORT_SYMBOL_GPL(watchdog_unregister_device); +static int __init watchdog_init(void) +{ + int err; + + watchdog_class = class_create(THIS_MODULE, "watchdog"); + if (IS_ERR(watchdog_class)) { + pr_err("couldn't create class\n"); + return PTR_ERR(watchdog_class); + } + + err = watchdog_dev_init(); + if (err < 0) { + class_destroy(watchdog_class); + return err; + } + + return 0; +} + +static void __exit watchdog_exit(void) +{ + watchdog_dev_exit(); + class_destroy(watchdog_class); + ida_destroy(&watchdog_ida); +} + +subsys_initcall(watchdog_init); +module_exit(watchdog_exit); + MODULE_AUTHOR("Alan Cox <alan@lxorguk.ukuu.org.uk>"); MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); MODULE_DESCRIPTION("WatchDog Timer Driver Core"); diff --git a/drivers/watchdog/watchdog_dev.h b/drivers/watchdog/watchdog_core.h index bc7612be25c..6c951418fca 100644 --- a/drivers/watchdog/watchdog_dev.h +++ b/drivers/watchdog/watchdog_core.h @@ -26,8 +26,12 @@ * This material is provided "AS-IS" and at no charge. */ +#define MAX_DOGS 32 /* Maximum number of watchdog devices */ + /* * Functions/procedures to be called by the core */ -int watchdog_dev_register(struct watchdog_device *); -int watchdog_dev_unregister(struct watchdog_device *); +extern int watchdog_dev_register(struct watchdog_device *); +extern int watchdog_dev_unregister(struct watchdog_device *); +extern int __init watchdog_dev_init(void); +extern void __exit watchdog_dev_exit(void); diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 8558da912c4..672d169bf1d 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -42,10 +42,12 @@ #include <linux/init.h> /* For __init/__exit/... */ #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ -/* make sure we only register one /dev/watchdog device */ -static unsigned long watchdog_dev_busy; +#include "watchdog_core.h" + +/* the dev_t structure to store the dynamically allocated watchdog devices */ +static dev_t watchdog_devt; /* the watchdog device behind /dev/watchdog */ -static struct watchdog_device *wdd; +static struct watchdog_device *old_wdd; /* * watchdog_ping: ping the watchdog. @@ -59,13 +61,26 @@ static struct watchdog_device *wdd; static int watchdog_ping(struct watchdog_device *wddev) { - if (test_bit(WDOG_ACTIVE, &wddev->status)) { - if (wddev->ops->ping) - return wddev->ops->ping(wddev); /* ping the watchdog */ - else - return wddev->ops->start(wddev); /* restart watchdog */ + int err = 0; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_ping; } - return 0; + + if (!watchdog_active(wddev)) + goto out_ping; + + if (wddev->ops->ping) + err = wddev->ops->ping(wddev); /* ping the watchdog */ + else + err = wddev->ops->start(wddev); /* restart watchdog */ + +out_ping: + mutex_unlock(&wddev->lock); + return err; } /* @@ -79,16 +94,25 @@ static int watchdog_ping(struct watchdog_device *wddev) static int watchdog_start(struct watchdog_device *wddev) { - int err; + int err = 0; - if (!test_bit(WDOG_ACTIVE, &wddev->status)) { - err = wddev->ops->start(wddev); - if (err < 0) - return err; + mutex_lock(&wddev->lock); - set_bit(WDOG_ACTIVE, &wddev->status); + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_start; } - return 0; + + if (watchdog_active(wddev)) + goto out_start; + + err = wddev->ops->start(wddev); + if (err == 0) + set_bit(WDOG_ACTIVE, &wddev->status); + +out_start: + mutex_unlock(&wddev->lock); + return err; } /* @@ -103,22 +127,155 @@ static int watchdog_start(struct watchdog_device *wddev) static int watchdog_stop(struct watchdog_device *wddev) { - int err = -EBUSY; + int err = 0; - if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) { - pr_info("%s: nowayout prevents watchdog to be stopped!\n", - wddev->info->identity); - return err; + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_stop; } - if (test_bit(WDOG_ACTIVE, &wddev->status)) { - err = wddev->ops->stop(wddev); - if (err < 0) - return err; + if (!watchdog_active(wddev)) + goto out_stop; + if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) { + dev_info(wddev->dev, "nowayout prevents watchdog being stopped!\n"); + err = -EBUSY; + goto out_stop; + } + + err = wddev->ops->stop(wddev); + if (err == 0) clear_bit(WDOG_ACTIVE, &wddev->status); + +out_stop: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_get_status: wrapper to get the watchdog status + * @wddev: the watchdog device to get the status from + * @status: the status of the watchdog device + * + * Get the watchdog's status flags. + */ + +static int watchdog_get_status(struct watchdog_device *wddev, + unsigned int *status) +{ + int err = 0; + + *status = 0; + if (!wddev->ops->status) + return -EOPNOTSUPP; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_status; } - return 0; + + *status = wddev->ops->status(wddev); + +out_status: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_set_timeout: set the watchdog timer timeout + * @wddev: the watchdog device to set the timeout for + * @timeout: timeout to set in seconds + */ + +static int watchdog_set_timeout(struct watchdog_device *wddev, + unsigned int timeout) +{ + int err; + + if ((wddev->ops->set_timeout == NULL) || + !(wddev->info->options & WDIOF_SETTIMEOUT)) + return -EOPNOTSUPP; + + if ((wddev->max_timeout != 0) && + (timeout < wddev->min_timeout || timeout > wddev->max_timeout)) + return -EINVAL; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_timeout; + } + + err = wddev->ops->set_timeout(wddev, timeout); + +out_timeout: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_get_timeleft: wrapper to get the time left before a reboot + * @wddev: the watchdog device to get the remaining time from + * @timeleft: the time that's left + * + * Get the time before a watchdog will reboot (if not pinged). + */ + +static int watchdog_get_timeleft(struct watchdog_device *wddev, + unsigned int *timeleft) +{ + int err = 0; + + *timeleft = 0; + if (!wddev->ops->get_timeleft) + return -EOPNOTSUPP; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_timeleft; + } + + *timeleft = wddev->ops->get_timeleft(wddev); + +out_timeleft: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_ioctl_op: call the watchdog drivers ioctl op if defined + * @wddev: the watchdog device to do the ioctl on + * @cmd: watchdog command + * @arg: argument pointer + */ + +static int watchdog_ioctl_op(struct watchdog_device *wddev, unsigned int cmd, + unsigned long arg) +{ + int err; + + if (!wddev->ops->ioctl) + return -ENOIOCTLCMD; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_ioctl; + } + + err = wddev->ops->ioctl(wddev, cmd, arg); + +out_ioctl: + mutex_unlock(&wddev->lock); + return err; } /* @@ -136,6 +293,7 @@ static int watchdog_stop(struct watchdog_device *wddev) static ssize_t watchdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { + struct watchdog_device *wdd = file->private_data; size_t i; char c; @@ -175,23 +333,24 @@ static ssize_t watchdog_write(struct file *file, const char __user *data, static long watchdog_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct watchdog_device *wdd = file->private_data; void __user *argp = (void __user *)arg; int __user *p = argp; unsigned int val; int err; - if (wdd->ops->ioctl) { - err = wdd->ops->ioctl(wdd, cmd, arg); - if (err != -ENOIOCTLCMD) - return err; - } + err = watchdog_ioctl_op(wdd, cmd, arg); + if (err != -ENOIOCTLCMD) + return err; switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user(argp, wdd->info, sizeof(struct watchdog_info)) ? -EFAULT : 0; case WDIOC_GETSTATUS: - val = wdd->ops->status ? wdd->ops->status(wdd) : 0; + err = watchdog_get_status(wdd, &val); + if (err) + return err; return put_user(val, p); case WDIOC_GETBOOTSTATUS: return put_user(wdd->bootstatus, p); @@ -215,15 +374,9 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, watchdog_ping(wdd); return 0; case WDIOC_SETTIMEOUT: - if ((wdd->ops->set_timeout == NULL) || - !(wdd->info->options & WDIOF_SETTIMEOUT)) - return -EOPNOTSUPP; if (get_user(val, p)) return -EFAULT; - if ((wdd->max_timeout != 0) && - (val < wdd->min_timeout || val > wdd->max_timeout)) - return -EINVAL; - err = wdd->ops->set_timeout(wdd, val); + err = watchdog_set_timeout(wdd, val); if (err < 0) return err; /* If the watchdog is active then we send a keepalive ping @@ -237,21 +390,21 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, return -EOPNOTSUPP; return put_user(wdd->timeout, p); case WDIOC_GETTIMELEFT: - if (!wdd->ops->get_timeleft) - return -EOPNOTSUPP; - - return put_user(wdd->ops->get_timeleft(wdd), p); + err = watchdog_get_timeleft(wdd, &val); + if (err) + return err; + return put_user(val, p); default: return -ENOTTY; } } /* - * watchdog_open: open the /dev/watchdog device. + * watchdog_open: open the /dev/watchdog* devices. * @inode: inode of device * @file: file handle to device * - * When the /dev/watchdog device gets opened, we start the watchdog. + * When the /dev/watchdog* device gets opened, we start the watchdog. * Watch out: the /dev/watchdog device is single open, so we make sure * it can only be opened once. */ @@ -259,6 +412,13 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, static int watchdog_open(struct inode *inode, struct file *file) { int err = -EBUSY; + struct watchdog_device *wdd; + + /* Get the corresponding watchdog device */ + if (imajor(inode) == MISC_MAJOR) + wdd = old_wdd; + else + wdd = container_of(inode->i_cdev, struct watchdog_device, cdev); /* the watchdog is single open! */ if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status)) @@ -275,6 +435,11 @@ static int watchdog_open(struct inode *inode, struct file *file) if (err < 0) goto out_mod; + file->private_data = wdd; + + if (wdd->ops->ref) + wdd->ops->ref(wdd); + /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ return nonseekable_open(inode, file); @@ -286,9 +451,9 @@ out: } /* - * watchdog_release: release the /dev/watchdog device. - * @inode: inode of device - * @file: file handle to device + * watchdog_release: release the watchdog device. + * @inode: inode of device + * @file: file handle to device * * This is the code for when /dev/watchdog gets closed. We will only * stop the watchdog when we have received the magic char (and nowayout @@ -297,6 +462,7 @@ out: static int watchdog_release(struct inode *inode, struct file *file) { + struct watchdog_device *wdd = file->private_data; int err = -EBUSY; /* @@ -310,7 +476,10 @@ static int watchdog_release(struct inode *inode, struct file *file) /* If the watchdog was not stopped, send a keepalive ping */ if (err < 0) { - pr_crit("%s: watchdog did not stop!\n", wdd->info->identity); + mutex_lock(&wdd->lock); + if (!test_bit(WDOG_UNREGISTERED, &wdd->status)) + dev_crit(wdd->dev, "watchdog did not stop!\n"); + mutex_unlock(&wdd->lock); watchdog_ping(wdd); } @@ -320,6 +489,10 @@ static int watchdog_release(struct inode *inode, struct file *file) /* make sure that /dev/watchdog can be re-opened */ clear_bit(WDOG_DEV_OPEN, &wdd->status); + /* Note wdd may be gone after this, do not use after this! */ + if (wdd->ops->unref) + wdd->ops->unref(wdd); + return 0; } @@ -338,62 +511,92 @@ static struct miscdevice watchdog_miscdev = { }; /* - * watchdog_dev_register: + * watchdog_dev_register: register a watchdog device * @watchdog: watchdog device * - * Register a watchdog device as /dev/watchdog. /dev/watchdog - * is actually a miscdevice and thus we set it up like that. + * Register a watchdog device including handling the legacy + * /dev/watchdog node. /dev/watchdog is actually a miscdevice and + * thus we set it up like that. */ int watchdog_dev_register(struct watchdog_device *watchdog) { - int err; - - /* Only one device can register for /dev/watchdog */ - if (test_and_set_bit(0, &watchdog_dev_busy)) { - pr_err("only one watchdog can use /dev/watchdog\n"); - return -EBUSY; + int err, devno; + + if (watchdog->id == 0) { + watchdog_miscdev.parent = watchdog->parent; + err = misc_register(&watchdog_miscdev); + if (err != 0) { + pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n", + watchdog->info->identity, WATCHDOG_MINOR, err); + if (err == -EBUSY) + pr_err("%s: a legacy watchdog module is probably present.\n", + watchdog->info->identity); + return err; + } + old_wdd = watchdog; } - wdd = watchdog; - - err = misc_register(&watchdog_miscdev); - if (err != 0) { - pr_err("%s: cannot register miscdev on minor=%d (err=%d)\n", - watchdog->info->identity, WATCHDOG_MINOR, err); - goto out; + /* Fill in the data structures */ + devno = MKDEV(MAJOR(watchdog_devt), watchdog->id); + cdev_init(&watchdog->cdev, &watchdog_fops); + watchdog->cdev.owner = watchdog->ops->owner; + + /* Add the device */ + err = cdev_add(&watchdog->cdev, devno, 1); + if (err) { + pr_err("watchdog%d unable to add device %d:%d\n", + watchdog->id, MAJOR(watchdog_devt), watchdog->id); + if (watchdog->id == 0) { + misc_deregister(&watchdog_miscdev); + old_wdd = NULL; + } } - - return 0; - -out: - wdd = NULL; - clear_bit(0, &watchdog_dev_busy); return err; } /* - * watchdog_dev_unregister: + * watchdog_dev_unregister: unregister a watchdog device * @watchdog: watchdog device * - * Deregister the /dev/watchdog device. + * Unregister the watchdog and if needed the legacy /dev/watchdog device. */ int watchdog_dev_unregister(struct watchdog_device *watchdog) { - /* Check that a watchdog device was registered in the past */ - if (!test_bit(0, &watchdog_dev_busy) || !wdd) - return -ENODEV; - - /* We can only unregister the watchdog device that was registered */ - if (watchdog != wdd) { - pr_err("%s: watchdog was not registered as /dev/watchdog\n", - watchdog->info->identity); - return -ENODEV; + mutex_lock(&watchdog->lock); + set_bit(WDOG_UNREGISTERED, &watchdog->status); + mutex_unlock(&watchdog->lock); + + cdev_del(&watchdog->cdev); + if (watchdog->id == 0) { + misc_deregister(&watchdog_miscdev); + old_wdd = NULL; } - - misc_deregister(&watchdog_miscdev); - wdd = NULL; - clear_bit(0, &watchdog_dev_busy); return 0; } + +/* + * watchdog_dev_init: init dev part of watchdog core + * + * Allocate a range of chardev nodes to use for watchdog devices + */ + +int __init watchdog_dev_init(void) +{ + int err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog"); + if (err < 0) + pr_err("watchdog: unable to allocate char dev region\n"); + return err; +} + +/* + * watchdog_dev_exit: exit dev part of watchdog core + * + * Release the range of chardev nodes used for watchdog devices + */ + +void __exit watchdog_dev_exit(void) +{ + unregister_chrdev_region(watchdog_devt, MAX_DOGS); +} diff --git a/drivers/xen/xen-acpi-processor.c b/drivers/xen/xen-acpi-processor.c index 0b48579a9cd..7ff2569e17a 100644 --- a/drivers/xen/xen-acpi-processor.c +++ b/drivers/xen/xen-acpi-processor.c @@ -29,6 +29,7 @@ #include <acpi/acpi_drivers.h> #include <acpi/processor.h> +#include <xen/xen.h> #include <xen/interface/platform.h> #include <asm/xen/hypercall.h> |