Commit 396c1004 authored by Julian Wiedmann's avatar Julian Wiedmann Committed by Heiko Carstens
Browse files

s390/qdio: let driver manage the QAOB



We are spending way too much effort on qdio-internal bookkeeping for
QAOB management & caching, and it's still not robust. Once qdio's
TX path has detached the QAOB from a PENDING buffer, we lost all
track of it until it shows up in a CQ notification again. So if the
device is torn down before that notification arrives, we leak the QAOB.

Just have the driver take care of it, and simply pass down a QAOB if
they want a TX with async-completion capability. For a buffer in PENDING
state that requires the QAOB for final completion, qeth can now also try
to recycle the buffer's QAOB rather than unconditionally freeing it.

This also eliminates the qdio_outbuf_state array, which was only needed
to transfer the aob->user1 tag from the driver to the qdio layer.

Signed-off-by: default avatarJulian Wiedmann <jwi@linux.ibm.com>
Acked-by: default avatarBenjamin Block <bblock@linux.ibm.com>
Signed-off-by: default avatarHeiko Carstens <hca@linux.ibm.com>
parent 95b3a8b4
Loading
Loading
Loading
Loading
+4 −18
Original line number Diff line number Diff line
@@ -246,21 +246,8 @@ struct slsb {
	u8 val[QDIO_MAX_BUFFERS_PER_Q];
} __attribute__ ((packed, aligned(256)));

/**
 * struct qdio_outbuf_state - SBAL related asynchronous operation information
 *   (for communication with upper layer programs)
 *   (only required for use with completion queues)
 * @user: pointer to upper layer program's state information related to SBAL
 *        (stored in user1 data of QAOB)
 */
struct qdio_outbuf_state {
	void *user;
};

#define CHSC_AC1_INITIATE_INPUTQ	0x80


/* qdio adapter-characteristics-1 flag */
#define CHSC_AC1_INITIATE_INPUTQ	0x80
#define AC1_SIGA_INPUT_NEEDED		0x40	/* process input queues */
#define AC1_SIGA_OUTPUT_NEEDED		0x20	/* process output queues */
#define AC1_SIGA_SYNC_NEEDED		0x10	/* ask hypervisor to sync */
@@ -338,7 +325,6 @@ typedef void qdio_handler_t(struct ccw_device *, unsigned int, int,
 * @int_parm: interruption parameter
 * @input_sbal_addr_array:  per-queue array, each element points to 128 SBALs
 * @output_sbal_addr_array: per-queue array, each element points to 128 SBALs
 * @output_sbal_state_array: no_output_qs * 128 state info (for CQ or NULL)
 */
struct qdio_initialize {
	unsigned char q_format;
@@ -357,7 +343,6 @@ struct qdio_initialize {
	unsigned long int_parm;
	struct qdio_buffer ***input_sbal_addr_array;
	struct qdio_buffer ***output_sbal_addr_array;
	struct qdio_outbuf_state *output_sbal_state_array;
};

#define QDIO_STATE_INACTIVE		0x00000002 /* after qdio_cleanup */
@@ -378,9 +363,10 @@ extern int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs,
extern int qdio_establish(struct ccw_device *cdev,
			  struct qdio_initialize *init_data);
extern int qdio_activate(struct ccw_device *);
extern struct qaob *qdio_allocate_aob(void);
extern void qdio_release_aob(struct qaob *);
extern int do_QDIO(struct ccw_device *, unsigned int, int, unsigned int,
		   unsigned int);
extern int do_QDIO(struct ccw_device *cdev, unsigned int callflags, int q_nr,
		   unsigned int bufnr, unsigned int count, struct qaob *aob);
extern int qdio_start_irq(struct ccw_device *cdev);
extern int qdio_stop_irq(struct ccw_device *cdev);
extern int qdio_get_next_buffers(struct ccw_device *, int, int *, int *);
+0 −10
Original line number Diff line number Diff line
@@ -181,12 +181,6 @@ struct qdio_input_q {
struct qdio_output_q {
	/* PCIs are enabled for the queue */
	int pci_out_enabled;
	/* cq: use asynchronous output buffers */
	int use_cq;
	/* cq: aobs used for particual SBAL */
	struct qaob **aobs;
	/* cq: sbal state related to asynchronous operation */
	struct qdio_outbuf_state *sbal_state;
	/* timer to check for more outbound work */
	struct timer_list timer;
	/* tasklet to check for completions */
@@ -379,12 +373,8 @@ int qdio_setup_irq(struct qdio_irq *irq_ptr, struct qdio_initialize *init_data);
void qdio_shutdown_irq(struct qdio_irq *irq);
void qdio_print_subchannel_info(struct qdio_irq *irq_ptr);
void qdio_free_queues(struct qdio_irq *irq_ptr);
void qdio_free_async_data(struct qdio_irq *irq_ptr);
int qdio_setup_init(void);
void qdio_setup_exit(void);
int qdio_enable_async_operation(struct qdio_output_q *q);
void qdio_disable_async_operation(struct qdio_output_q *q);
struct qaob *qdio_allocate_aob(void);

int debug_get_buf_state(struct qdio_q *q, unsigned int bufnr,
			unsigned char *state);
+8 −55
Original line number Diff line number Diff line
@@ -517,24 +517,6 @@ static inline int qdio_inbound_q_done(struct qdio_q *q, unsigned int start)
	return 1;
}

static inline unsigned long qdio_aob_for_buffer(struct qdio_output_q *q,
					int bufnr)
{
	unsigned long phys_aob = 0;

	if (!q->aobs[bufnr]) {
		struct qaob *aob = qdio_allocate_aob();
		q->aobs[bufnr] = aob;
	}
	if (q->aobs[bufnr]) {
		q->aobs[bufnr]->user1 = (u64) q->sbal_state[bufnr].user;
		phys_aob = virt_to_phys(q->aobs[bufnr]);
		WARN_ON_ONCE(phys_aob & 0xFF);
	}

	return phys_aob;
}

static inline int qdio_tasklet_schedule(struct qdio_q *q)
{
	if (likely(q->irq_ptr->state == QDIO_IRQ_STATE_ACTIVE)) {
@@ -548,7 +530,6 @@ static int get_outbound_buffer_frontier(struct qdio_q *q, unsigned int start,
					unsigned int *error)
{
	unsigned char state = 0;
	unsigned int i;
	int count;

	q->timestamp = get_tod_clock_fast();
@@ -570,10 +551,6 @@ static int get_outbound_buffer_frontier(struct qdio_q *q, unsigned int start,

	switch (state) {
	case SLSB_P_OUTPUT_PENDING:
		/* detach the utilized QAOBs: */
		for (i = 0; i < count; i++)
			q->u.out.aobs[QDIO_BUFNR(start + i)] = NULL;

		*error = QDIO_ERROR_SLSB_PENDING;
		fallthrough;
	case SLSB_P_OUTPUT_EMPTY:
@@ -999,7 +976,6 @@ int qdio_free(struct ccw_device *cdev)
	cdev->private->qdio_data = NULL;
	mutex_unlock(&irq_ptr->setup_mutex);

	qdio_free_async_data(irq_ptr);
	qdio_free_queues(irq_ptr);
	free_page((unsigned long) irq_ptr->qdr);
	free_page(irq_ptr->chsc_page);
@@ -1075,28 +1051,6 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs,
}
EXPORT_SYMBOL_GPL(qdio_allocate);

static void qdio_detect_hsicq(struct qdio_irq *irq_ptr)
{
	struct qdio_q *q = irq_ptr->input_qs[0];
	int i, use_cq = 0;

	if (irq_ptr->nr_input_qs > 1 && queue_type(q) == QDIO_IQDIO_QFMT)
		use_cq = 1;

	for_each_output_queue(irq_ptr, q, i) {
		if (use_cq) {
			if (multicast_outbound(q))
				continue;
			if (qdio_enable_async_operation(&q->u.out) < 0) {
				use_cq = 0;
				continue;
			}
		} else
			qdio_disable_async_operation(&q->u.out);
	}
	DBF_EVENT("use_cq:%d", use_cq);
}

static void qdio_trace_init_data(struct qdio_irq *irq,
				 struct qdio_initialize *data)
{
@@ -1191,8 +1145,6 @@ int qdio_establish(struct ccw_device *cdev,

	qdio_setup_ssqd_info(irq_ptr);

	qdio_detect_hsicq(irq_ptr);

	/* qebsm is now setup if available, initialize buffer states */
	qdio_init_buf_states(irq_ptr);

@@ -1297,9 +1249,11 @@ static int handle_inbound(struct qdio_q *q, unsigned int callflags,
 * @callflags: flags
 * @bufnr: first buffer to process
 * @count: how many buffers are filled
 * @aob: asynchronous operation block
 */
static int handle_outbound(struct qdio_q *q, unsigned int callflags,
			   unsigned int bufnr, unsigned int count)
			   unsigned int bufnr, unsigned int count,
			   struct qaob *aob)
{
	const unsigned int scan_threshold = q->irq_ptr->scan_threshold;
	unsigned char state = 0;
@@ -1320,11 +1274,9 @@ static int handle_outbound(struct qdio_q *q, unsigned int callflags,
		q->u.out.pci_out_enabled = 0;

	if (queue_type(q) == QDIO_IQDIO_QFMT) {
		unsigned long phys_aob = 0;

		if (q->u.out.use_cq && count == 1)
			phys_aob = qdio_aob_for_buffer(&q->u.out, bufnr);
		unsigned long phys_aob = aob ? virt_to_phys(aob) : 0;

		WARN_ON_ONCE(!IS_ALIGNED(phys_aob, 256));
		rc = qdio_kick_outbound_q(q, count, phys_aob);
	} else if (need_siga_sync(q)) {
		rc = qdio_siga_sync_q(q);
@@ -1359,9 +1311,10 @@ static int handle_outbound(struct qdio_q *q, unsigned int callflags,
 * @q_nr: queue number
 * @bufnr: buffer number
 * @count: how many buffers to process
 * @aob: asynchronous operation block (outbound only)
 */
int do_QDIO(struct ccw_device *cdev, unsigned int callflags,
	    int q_nr, unsigned int bufnr, unsigned int count)
	    int q_nr, unsigned int bufnr, unsigned int count, struct qaob *aob)
{
	struct qdio_irq *irq_ptr = cdev->private->qdio_data;

@@ -1383,7 +1336,7 @@ int do_QDIO(struct ccw_device *cdev, unsigned int callflags,
				      callflags, bufnr, count);
	else if (callflags & QDIO_FLAG_SYNC_OUTPUT)
		return handle_outbound(irq_ptr->output_qs[q_nr],
				       callflags, bufnr, count);
				       callflags, bufnr, count, aob);
	return -EINVAL;
}
EXPORT_SYMBOL_GPL(do_QDIO);
+1 −48
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ struct qaob *qdio_allocate_aob(void)
{
	return kmem_cache_zalloc(qdio_aob_cache, GFP_ATOMIC);
}
EXPORT_SYMBOL_GPL(qdio_allocate_aob);

void qdio_release_aob(struct qaob *aob)
{
@@ -247,8 +248,6 @@ static void setup_queues(struct qdio_irq *irq_ptr,
			 struct qdio_initialize *qdio_init)
{
	struct qdio_q *q;
	struct qdio_outbuf_state *output_sbal_state_array =
				  qdio_init->output_sbal_state_array;
	int i;

	for_each_input_queue(irq_ptr, q, i) {
@@ -265,9 +264,6 @@ static void setup_queues(struct qdio_irq *irq_ptr,
		DBF_EVENT("outq:%1d", i);
		setup_queues_misc(q, irq_ptr, qdio_init->output_handler, i);

		q->u.out.sbal_state = output_sbal_state_array;
		output_sbal_state_array += QDIO_MAX_BUFFERS_PER_Q;

		q->is_input_q = 0;
		setup_storage_lists(q, irq_ptr,
				    qdio_init->output_sbal_addr_array[i], i);
@@ -372,30 +368,6 @@ void qdio_setup_ssqd_info(struct qdio_irq *irq_ptr)
	DBF_EVENT("3:%4x qib:%4x", irq_ptr->ssqd_desc.qdioac3, irq_ptr->qib.ac);
}

void qdio_free_async_data(struct qdio_irq *irq_ptr)
{
	struct qdio_q *q;
	int i;

	for (i = 0; i < irq_ptr->max_output_qs; i++) {
		q = irq_ptr->output_qs[i];
		if (q->u.out.use_cq) {
			unsigned int n;

			for (n = 0; n < QDIO_MAX_BUFFERS_PER_Q; n++) {
				struct qaob *aob = q->u.out.aobs[n];

				if (aob) {
					qdio_release_aob(aob);
					q->u.out.aobs[n] = NULL;
				}
			}

			qdio_disable_async_operation(&q->u.out);
		}
	}
}

static void qdio_fill_qdr_desc(struct qdesfmt0 *desc, struct qdio_q *queue)
{
	desc->sliba = virt_to_phys(queue->slib);
@@ -545,25 +517,6 @@ void qdio_print_subchannel_info(struct qdio_irq *irq_ptr)
	printk(KERN_INFO "%s", s);
}

int qdio_enable_async_operation(struct qdio_output_q *outq)
{
	outq->aobs = kcalloc(QDIO_MAX_BUFFERS_PER_Q, sizeof(struct qaob *),
			     GFP_KERNEL);
	if (!outq->aobs) {
		outq->use_cq = 0;
		return -ENOMEM;
	}
	outq->use_cq = 1;
	return 0;
}

void qdio_disable_async_operation(struct qdio_output_q *q)
{
	kfree(q->aobs);
	q->aobs = NULL;
	q->use_cq = 0;
}

int __init qdio_setup_init(void)
{
	int rc;
+1 −2
Original line number Diff line number Diff line
@@ -437,6 +437,7 @@ struct qeth_qdio_out_buffer {

	struct qeth_qdio_out_q *q;
	struct list_head list_entry;
	struct qaob *aob;
};

struct qeth_card;
@@ -499,7 +500,6 @@ struct qeth_out_q_stats {
struct qeth_qdio_out_q {
	struct qdio_buffer *qdio_bufs[QDIO_MAX_BUFFERS_PER_Q];
	struct qeth_qdio_out_buffer *bufs[QDIO_MAX_BUFFERS_PER_Q];
	struct qdio_outbuf_state *bufstates; /* convenience pointer */
	struct list_head pending_bufs;
	struct qeth_out_q_stats stats;
	spinlock_t lock;
@@ -563,7 +563,6 @@ struct qeth_qdio_info {
	/* output */
	unsigned int no_out_queues;
	struct qeth_qdio_out_q *out_qs[QETH_MAX_OUT_QUEUES];
	struct qdio_outbuf_state *out_bufstates;

	/* priority queueing */
	int do_prio_queueing;
Loading