source: mainline/uspace/drv/block/isa-ide/isa-ide.c@ 1c7b0db7

Last change on this file since 1c7b0db7 was 07039850, checked in by Jiri Svoboda <jiri@…>, 6 months ago

Implement quiesce in ISA and PCI IDE and in PC Floppy.

  • Property mode set to 100644
File size: 13.1 KB
RevLine 
[f8ef660]1/*
[a796812c]2 * Copyright (c) 2025 Jiri Svoboda
[f8ef660]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
[59c0f478]29/** @addtogroup isa-ide
[f8ef660]30 * @{
31 */
32
33/**
34 * @file
[59c0f478]35 * @brief ISA IDE driver
[f8ef660]36 */
37
38#include <ddi.h>
[64cf7a3]39#include <ddf/interrupt.h>
[9f391e9]40#include <ddf/log.h>
[f8ef660]41#include <async.h>
42#include <as.h>
[1e4cada]43#include <fibril_synch.h>
[bb0eab1]44#include <stdint.h>
[8d2dd7f2]45#include <stdbool.h>
46#include <stdio.h>
47#include <stddef.h>
[19f857a]48#include <str.h>
[fb6f1a5]49#include <inttypes.h>
[f8ef660]50#include <errno.h>
51
[59c0f478]52#include "isa-ide.h"
[66cb7a2]53#include "main.h"
[f8ef660]54
[646849b3]55static errno_t isa_ide_init_io(isa_ide_channel_t *);
56static void isa_ide_fini_io(isa_ide_channel_t *);
57static errno_t isa_ide_init_irq(isa_ide_channel_t *);
58static void isa_ide_fini_irq(isa_ide_channel_t *);
[60744cb]59static void isa_ide_irq_handler(ipc_call_t *, void *);
[59c0f478]60
61static void isa_ide_write_data_16(void *, uint16_t *, size_t);
62static void isa_ide_read_data_16(void *, uint16_t *, size_t);
63static void isa_ide_write_cmd_8(void *, uint16_t, uint8_t);
64static uint8_t isa_ide_read_cmd_8(void *, uint16_t);
65static void isa_ide_write_ctl_8(void *, uint16_t, uint8_t);
66static uint8_t isa_ide_read_ctl_8(void *, uint16_t);
67static errno_t isa_ide_irq_enable(void *);
68static errno_t isa_ide_irq_disable(void *);
69static errno_t isa_ide_add_device(void *, unsigned, void *);
70static errno_t isa_ide_remove_device(void *, unsigned);
71static void isa_ide_msg_debug(void *, char *);
72static void isa_ide_msg_note(void *, char *);
73static void isa_ide_msg_warn(void *, char *);
74static void isa_ide_msg_error(void *, char *);
75
76static const irq_pio_range_t isa_ide_irq_ranges[] = {
[64cf7a3]77 {
78 .base = 0,
79 .size = sizeof(ata_cmd_t)
80 }
81};
82
[59c0f478]83/** IDE interrupt pseudo code. */
84static const irq_cmd_t isa_ide_irq_cmds[] = {
[64cf7a3]85 {
86 .cmd = CMD_PIO_READ_8,
87 .addr = NULL, /* will be patched in run-time */
88 .dstarg = 1
89 },
90 {
91 .cmd = CMD_ACCEPT
92 }
93};
94
[646849b3]95/** Initialize ISA IDE channel. */
96errno_t isa_ide_channel_init(isa_ide_ctrl_t *ctrl, isa_ide_channel_t *chan,
97 unsigned chan_id, isa_ide_hwres_t *res)
[f8ef660]98{
[b7fd2a0]99 errno_t rc;
[64cf7a3]100 bool irq_inited = false;
[2791fbb7]101 ata_params_t params;
[f8ef660]102
[07039850]103 ddf_msg(LVL_DEBUG, "isa_ide_channel_init()");
[f8ef660]104
[a796812c]105 memset(&params, 0, sizeof(params));
106
[646849b3]107 chan->ctrl = ctrl;
108 chan->chan_id = chan_id;
109 fibril_mutex_initialize(&chan->lock);
110 if (chan_id == 0) {
111 chan->cmd_physical = res->cmd1;
112 chan->ctl_physical = res->ctl1;
113 chan->irq = res->irq1;
114 } else {
115 chan->cmd_physical = res->cmd2;
116 chan->ctl_physical = res->ctl2;
117 chan->irq = res->irq2;
118 }
[e092dc5]119
[646849b3]120 ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) chan->cmd_physical,
121 (void *) chan->ctl_physical);
[f8ef660]122
[2791fbb7]123 ddf_msg(LVL_DEBUG, "Init I/O");
[646849b3]124 rc = isa_ide_init_io(chan);
[66cb7a2]125 if (rc != EOK)
126 return rc;
[f8ef660]127
[2791fbb7]128 ddf_msg(LVL_DEBUG, "Init IRQ");
[646849b3]129 rc = isa_ide_init_irq(chan);
[2791fbb7]130 if (rc != EOK) {
131 ddf_msg(LVL_NOTE, "init IRQ failed");
[64cf7a3]132 return rc;
[31de325]133 }
[1806e5d]134
[2791fbb7]135 irq_inited = true;
[66cb7a2]136
[07039850]137 ddf_msg(LVL_DEBUG, "isa_ide_channel_init(): Initialize IDE channel");
[2791fbb7]138
[646849b3]139 params.arg = (void *)chan;
[a796812c]140 params.use_dma = false;
[646849b3]141 params.have_irq = (chan->irq >= 0) ? true : false;
[59c0f478]142 params.write_data_16 = isa_ide_write_data_16;
143 params.read_data_16 = isa_ide_read_data_16;
144 params.write_cmd_8 = isa_ide_write_cmd_8;
145 params.read_cmd_8 = isa_ide_read_cmd_8;
146 params.write_ctl_8 = isa_ide_write_ctl_8;
147 params.read_ctl_8 = isa_ide_read_ctl_8;
148 params.irq_enable = isa_ide_irq_enable;
149 params.irq_disable = isa_ide_irq_disable;
150 params.add_device = isa_ide_add_device;
151 params.remove_device = isa_ide_remove_device;
152 params.msg_debug = isa_ide_msg_debug;
153 params.msg_note = isa_ide_msg_note;
154 params.msg_warn = isa_ide_msg_warn;
155 params.msg_error = isa_ide_msg_error;
[2791fbb7]156
[646849b3]157 rc = ata_channel_create(&params, &chan->channel);
[2791fbb7]158 if (rc != EOK)
159 goto error;
[1806e5d]160
[646849b3]161 rc = ata_channel_initialize(chan->channel);
[2791fbb7]162 if (rc != EOK)
[66cb7a2]163 goto error;
[1806e5d]164
[07039850]165 ddf_msg(LVL_DEBUG, "isa_ide_channel_init: DONE");
[66cb7a2]166 return EOK;
167error:
[145d4e2e]168 if (chan->channel != NULL) {
169 (void) ata_channel_destroy(chan->channel);
170 chan->channel = NULL;
171 }
[64cf7a3]172 if (irq_inited)
[646849b3]173 isa_ide_fini_irq(chan);
174 isa_ide_fini_io(chan);
[66cb7a2]175 return rc;
[1806e5d]176}
177
[646849b3]178/** Finalize ISA IDE channel. */
179errno_t isa_ide_channel_fini(isa_ide_channel_t *chan)
[66cb7a2]180{
[b7fd2a0]181 errno_t rc;
[66cb7a2]182
[07039850]183 ddf_msg(LVL_DEBUG, ": isa_ide_channel_fini()");
[e092dc5]184
[646849b3]185 fibril_mutex_lock(&chan->lock);
[66cb7a2]186
[646849b3]187 rc = ata_channel_destroy(chan->channel);
[2791fbb7]188 if (rc != EOK) {
[646849b3]189 fibril_mutex_unlock(&chan->lock);
[2791fbb7]190 return rc;
[66cb7a2]191 }
192
[646849b3]193 isa_ide_fini_irq(chan);
194 isa_ide_fini_io(chan);
195 fibril_mutex_unlock(&chan->lock);
[66cb7a2]196
197 return EOK;
[e092dc5]198}
199
[07039850]200/** Quiesce ISA IDE channel. */
201void isa_ide_channel_quiesce(isa_ide_channel_t *chan)
202{
203 ddf_msg(LVL_DEBUG, ": isa_ide_channel_quiesce()");
204
205 fibril_mutex_lock(&chan->lock);
206 ata_channel_quiesce(chan->channel);
207 fibril_mutex_unlock(&chan->lock);
208}
209
[66cb7a2]210/** Enable device I/O. */
[646849b3]211static errno_t isa_ide_init_io(isa_ide_channel_t *chan)
[f8ef660]212{
[b7fd2a0]213 errno_t rc;
[b16e77d]214 void *vaddr;
[66cb7a2]215
[646849b3]216 rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
[f8ef660]217 if (rc != EOK) {
[9f391e9]218 ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
[f8ef660]219 return rc;
220 }
[66cb7a2]221
[646849b3]222 chan->cmd = vaddr;
[66cb7a2]223
[646849b3]224 rc = pio_enable((void *) chan->ctl_physical, sizeof(ata_ctl_t), &vaddr);
[f8ef660]225 if (rc != EOK) {
[9f391e9]226 ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
[f8ef660]227 return rc;
228 }
[66cb7a2]229
[646849b3]230 chan->ctl = vaddr;
[f8ef660]231 return EOK;
232}
233
[66cb7a2]234/** Clean up device I/O. */
[646849b3]235static void isa_ide_fini_io(isa_ide_channel_t *chan)
[f8ef660]236{
[646849b3]237 (void) chan;
[66cb7a2]238 /* XXX TODO */
[f8ef660]239}
240
[64cf7a3]241/** Initialize IRQ. */
[646849b3]242static errno_t isa_ide_init_irq(isa_ide_channel_t *chan)
[64cf7a3]243{
244 irq_code_t irq_code;
245 irq_pio_range_t *ranges;
246 irq_cmd_t *cmds;
247 errno_t rc;
248
[646849b3]249 if (chan->irq < 0)
[64cf7a3]250 return EOK;
251
[59c0f478]252 ranges = malloc(sizeof(isa_ide_irq_ranges));
[64cf7a3]253 if (ranges == NULL)
254 return ENOMEM;
255
[59c0f478]256 cmds = malloc(sizeof(isa_ide_irq_cmds));
[64cf7a3]257 if (cmds == NULL) {
258 free(cmds);
259 return ENOMEM;
260 }
261
[59c0f478]262 memcpy(ranges, &isa_ide_irq_ranges, sizeof(isa_ide_irq_ranges));
[646849b3]263 ranges[0].base = chan->cmd_physical;
[59c0f478]264 memcpy(cmds, &isa_ide_irq_cmds, sizeof(isa_ide_irq_cmds));
[646849b3]265 cmds[0].addr = &chan->cmd->status;
[64cf7a3]266
[59c0f478]267 irq_code.rangecount = sizeof(isa_ide_irq_ranges) / sizeof(irq_pio_range_t);
[64cf7a3]268 irq_code.ranges = ranges;
[59c0f478]269 irq_code.cmdcount = sizeof(isa_ide_irq_cmds) / sizeof(irq_cmd_t);
[64cf7a3]270 irq_code.cmds = cmds;
271
[646849b3]272 ddf_msg(LVL_NOTE, "IRQ %d", chan->irq);
[60744cb]273 rc = register_interrupt_handler(chan->ctrl->dev, chan->irq,
[646849b3]274 isa_ide_irq_handler, (void *)chan, &irq_code, &chan->ihandle);
[64cf7a3]275 if (rc != EOK) {
276 ddf_msg(LVL_ERROR, "Error registering IRQ.");
277 goto error;
278 }
279
[2791fbb7]280 ddf_msg(LVL_DEBUG, "Interrupt handler registered");
[64cf7a3]281 free(ranges);
282 free(cmds);
283 return EOK;
284error:
285 free(ranges);
286 free(cmds);
287 return rc;
288}
289
290/** Clean up IRQ. */
[646849b3]291static void isa_ide_fini_irq(isa_ide_channel_t *chan)
[64cf7a3]292{
293 errno_t rc;
294 async_sess_t *parent_sess;
295
[646849b3]296 parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
[64cf7a3]297
[646849b3]298 rc = hw_res_disable_interrupt(parent_sess, chan->irq);
[64cf7a3]299 if (rc != EOK)
300 ddf_msg(LVL_ERROR, "Error disabling IRQ.");
301
[646849b3]302 (void) unregister_interrupt_handler(chan->ctrl->dev, chan->ihandle);
[64cf7a3]303}
304
[2791fbb7]305/** Interrupt handler.
[7a56e33e]306 *
[2791fbb7]307 * @param call Call data
[60744cb]308 * @param arg Argument (isa_ide_channel_t *)
[54d0ddc]309 */
[60744cb]310static void isa_ide_irq_handler(ipc_call_t *call, void *arg)
[54d0ddc]311{
[60744cb]312 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[54d0ddc]313 uint8_t status;
[2791fbb7]314 async_sess_t *parent_sess;
[7a56e33e]315
[2791fbb7]316 status = ipc_get_arg1(call);
[646849b3]317 ata_channel_irq(chan->channel, status);
[7a56e33e]318
[646849b3]319 parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
320 hw_res_clear_interrupt(parent_sess, chan->irq);
[54d0ddc]321}
322
[2791fbb7]323/** Write the data register callback handler.
[88743b5]324 *
[646849b3]325 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]326 * @param data Data
327 * @param nwords Number of words to write
[88743b5]328 */
[59c0f478]329static void isa_ide_write_data_16(void *arg, uint16_t *data, size_t nwords)
[88743b5]330{
[646849b3]331 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[88743b5]332 size_t i;
333
[2791fbb7]334 for (i = 0; i < nwords; i++)
[646849b3]335 pio_write_16(&chan->cmd->data_port, data[i]);
[88743b5]336}
337
[2791fbb7]338/** Read the data register callback handler.
[88743b5]339 *
[646849b3]340 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]341 * @param buf Destination buffer
342 * @param nwords Number of words to read
[88743b5]343 */
[59c0f478]344static void isa_ide_read_data_16(void *arg, uint16_t *buf, size_t nwords)
[88743b5]345{
[646849b3]346 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[2791fbb7]347 size_t i;
[c2844735]348
[2791fbb7]349 for (i = 0; i < nwords; i++)
[646849b3]350 buf[i] = pio_read_16(&chan->cmd->data_port);
[c2844735]351}
352
[2791fbb7]353/** Write command register callback handler.
[c2844735]354 *
[646849b3]355 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]356 * @param off Register offset
357 * @param value Value to write to command register
[c2844735]358 */
[59c0f478]359static void isa_ide_write_cmd_8(void *arg, uint16_t off, uint8_t value)
[c2844735]360{
[646849b3]361 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[c2844735]362
[646849b3]363 pio_write_8(((ioport8_t *)chan->cmd) + off, value);
[88743b5]364}
365
[2791fbb7]366/** Read command register callback handler.
[aa893e0]367 *
[646849b3]368 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]369 * @param off Register offset
370 * @return value Value read from command register
[aa893e0]371 */
[59c0f478]372static uint8_t isa_ide_read_cmd_8(void *arg, uint16_t off)
[0d247f5]373{
[646849b3]374 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[0d247f5]375
[646849b3]376 return pio_read_8(((ioport8_t *)chan->cmd) + off);
[0d247f5]377}
378
[2791fbb7]379/** Write control register callback handler.
[4046b2f4]380 *
[646849b3]381 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]382 * @param off Register offset
383 * @param value Value to write to control register
[4046b2f4]384 */
[59c0f478]385static void isa_ide_write_ctl_8(void *arg, uint16_t off, uint8_t value)
[4046b2f4]386{
[646849b3]387 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[35b8bfe]388
[646849b3]389 pio_write_8(((ioport8_t *)chan->ctl) + off, value);
[4046b2f4]390}
391
[2791fbb7]392/** Read control register callback handler.
[54d0ddc]393 *
[646849b3]394 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]395 * @param off Register offset
396 * @return value Value read from control register
[54d0ddc]397 */
[59c0f478]398static uint8_t isa_ide_read_ctl_8(void *arg, uint16_t off)
[f8ef660]399{
[646849b3]400 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[f8ef660]401
[646849b3]402 return pio_read_8(((ioport8_t *)chan->ctl) + off);
[f8ef660]403}
404
[2791fbb7]405/** Enable IRQ callback handler
[54d0ddc]406 *
[646849b3]407 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]408 * @return EOK on success or an error code
[54d0ddc]409 */
[59c0f478]410static errno_t isa_ide_irq_enable(void *arg)
[d9f4c76]411{
[646849b3]412 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[2791fbb7]413 async_sess_t *parent_sess;
[b7fd2a0]414 errno_t rc;
[d9f4c76]415
[646849b3]416 ddf_msg(LVL_DEBUG, "Enable IRQ %d for channel %u",
417 chan->irq, chan->chan_id);
[d9f4c76]418
[646849b3]419 parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
[a7de7907]420
[646849b3]421 rc = hw_res_enable_interrupt(parent_sess, chan->irq);
[2791fbb7]422 if (rc != EOK) {
423 ddf_msg(LVL_ERROR, "Error enabling IRQ.");
424 return rc;
[31de325]425 }
426
[2791fbb7]427 return EOK;
[d9f4c76]428}
429
[2791fbb7]430/** Disable IRQ callback handler
[4abe919]431 *
[646849b3]432 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]433 * @return EOK on success or an error code
[4abe919]434 */
[59c0f478]435static errno_t isa_ide_irq_disable(void *arg)
[4abe919]436{
[646849b3]437 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
[2791fbb7]438 async_sess_t *parent_sess;
[b7fd2a0]439 errno_t rc;
[4abe919]440
[2791fbb7]441 ddf_msg(LVL_DEBUG, "Disable IRQ");
[4abe919]442
[646849b3]443 parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
[4abe919]444
[646849b3]445 rc = hw_res_disable_interrupt(parent_sess, chan->irq);
[2791fbb7]446 if (rc != EOK) {
[646849b3]447 ddf_msg(LVL_ERROR, "Error disabling IRQ.");
[2791fbb7]448 return rc;
[4abe919]449 }
450
[2791fbb7]451 return EOK;
[4abe919]452}
453
[2791fbb7]454/** Add ATA device callback handler.
[21989e5]455 *
[646849b3]456 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]457 * @param idx Device index
458 * $param charg Connection handler argument
459 * @return EOK on success or an error code
[21989e5]460 */
[59c0f478]461static errno_t isa_ide_add_device(void *arg, unsigned idx, void *charg)
[21989e5]462{
[646849b3]463 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
464 return isa_ide_fun_create(chan, idx, charg);
[21989e5]465}
466
[2791fbb7]467/** Remove ATA device callback handler.
[5048be7]468 *
[646849b3]469 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]470 * @param idx Device index
471 * @return EOK on success or an error code
[5048be7]472 */
[59c0f478]473static errno_t isa_ide_remove_device(void *arg, unsigned idx)
[5048be7]474{
[646849b3]475 isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
476 return isa_ide_fun_remove(chan, idx);
[5048be7]477}
478
[2791fbb7]479/** Debug message callback handler.
[96cbd18]480 *
[646849b3]481 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]482 * @param msg Message
[5048be7]483 */
[59c0f478]484static void isa_ide_msg_debug(void *arg, char *msg)
[5048be7]485{
[2791fbb7]486 (void)arg;
487 ddf_msg(LVL_DEBUG, "%s", msg);
[5048be7]488}
489
[2791fbb7]490/** Notice message callback handler.
[54d0ddc]491 *
[646849b3]492 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]493 * @param msg Message
[54d0ddc]494 */
[59c0f478]495static void isa_ide_msg_note(void *arg, char *msg)
[54d0ddc]496{
[2791fbb7]497 (void)arg;
498 ddf_msg(LVL_NOTE, "%s", msg);
[54d0ddc]499}
500
[2791fbb7]501/** Warning message callback handler.
[64cf7a3]502 *
[646849b3]503 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]504 * @param msg Message
[64cf7a3]505 */
[59c0f478]506static void isa_ide_msg_warn(void *arg, char *msg)
[64cf7a3]507{
[2791fbb7]508 (void)arg;
509 ddf_msg(LVL_WARN, "%s", msg);
[64cf7a3]510}
511
[2791fbb7]512/** Error message callback handler.
[64cf7a3]513 *
[646849b3]514 * @param arg Argument (isa_ide_channel_t *)
[2791fbb7]515 * @param msg Message
[64cf7a3]516 */
[59c0f478]517static void isa_ide_msg_error(void *arg, char *msg)
[64cf7a3]518{
[2791fbb7]519 (void)arg;
520 ddf_msg(LVL_ERROR, "%s", msg);
[64cf7a3]521}
522
[f8ef660]523/**
524 * @}
525 */
Note: See TracBrowser for help on using the repository browser.