aboutsummaryrefslogtreecommitdiffstats
path: root/3rdparty/googletest/googlemock/docs/v1_7/FrequentlyAskedQuestions.md
blob: fa21233aa25504fca63db13bf1be58e2f5c394f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
Please send your questions to the
[googlemock](http://groups.google.com/group/googlemock) discussion
group. If you need help with compiler errors, make sure you have
tried [Google Mock Doctor](#How_am_I_supposed_to_make_sense_of_these_horrible_template_error.md) first.

## When I call a method on my mock object, the method for the real object is invoked instead.  What's the problem? ##

In order for a method to be mocked, it must be _virtual_, unless you use the [high-perf dependency injection technique](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Mocking_Nonvirtual_Methods).

## I wrote some matchers.  After I upgraded to a new version of Google Mock, they no longer compile.  What's going on? ##

After version 1.4.0 of Google Mock was released, we had an idea on how
to make it easier to write matchers that can generate informative
messages efficiently.  We experimented with this idea and liked what
we saw.  Therefore we decided to implement it.

Unfortunately, this means that if you have defined your own matchers
by implementing `MatcherInterface` or using `MakePolymorphicMatcher()`,
your definitions will no longer compile.  Matchers defined using the
`MATCHER*` family of macros are not affected.

Sorry for the hassle if your matchers are affected.  We believe it's
in everyone's long-term interest to make this change sooner than
later.  Fortunately, it's usually not hard to migrate an existing
matcher to the new API.  Here's what you need to do:

If you wrote your matcher like this:
```
// Old matcher definition that doesn't work with the latest
// Google Mock.
using ::testing::MatcherInterface;
...
class MyWonderfulMatcher : public MatcherInterface<MyType> {
 public:
  ...
  virtual bool Matches(MyType value) const {
    // Returns true if value matches.
    return value.GetFoo() > 5;
  }
  ...
};
```

you'll need to change it to:
```
// New matcher definition that works with the latest Google Mock.
using ::testing::MatcherInterface;
using ::testing::MatchResultListener;
...
class MyWonderfulMatcher : public MatcherInterface<MyType> {
 public:
  ...
  virtual bool MatchAndExplain(MyType value,
                               MatchResultListener* listener) const {
    // Returns true if value matches.
    return value.GetFoo() > 5;
  }
  ...
};
```
(i.e. rename `Matches()` to `MatchAndExplain()` and give it a second
argument of type `MatchResultListener*`.)

If you were also using `ExplainMatchResultTo()` to improve the matcher
message:
```
// Old matcher definition that doesn't work with the lastest
// Google Mock.
using ::testing::MatcherInterface;
...
class MyWonderfulMatcher : public MatcherInterface<MyType> {
 public:
  ...
  virtual bool Matches(MyType value) const {
    // Returns true if value matches.
    return value.GetFoo() > 5;
  }

  virtual void ExplainMatchResultTo(MyType value,
                                    ::std::ostream* os) const {
    // Prints some helpful information to os to help
    // a user understand why value matches (or doesn't match).
    *os << "the Foo property is " << value.GetFoo();
  }
  ...
};
```

you should move the logic of `ExplainMatchResultTo()` into
`MatchAndExplain()`, using the `MatchResultListener` argument where
the `::std::ostream` was used:
```
// New matcher definition that works with the latest Google Mock.
using ::testing::MatcherInterface;
using ::testing::MatchResultListener;
...
class MyWonderfulMatcher : public MatcherInterface<MyType> {
 public:
  ...
  virtual bool MatchAndExplain(MyType value,
                               MatchResultListener* listener) const {
    // Returns true if value matches.
    *listener << "the Foo property is " << value.GetFoo();
    return value.GetFoo() > 5;
  }
  ...
};
```

If your matcher is defined using `MakePolymorphicMatcher()`:
```
// Old matcher definition that doesn't work with the latest
// Google Mock.
using ::testing::MakePolymorphicMatcher;
...
class MyGreatMatcher {
 public:
  ...
  bool Matches(MyType value) const {
    // Returns true if value matches.
    return value.GetBar() < 42;
  }
  ...
};
... MakePolymorphicMatcher(MyGreatMatcher()) ...
```

you should rename the `Matches()` method to `MatchAndExplain()` and
add a `MatchResultListener*` argument (the same as what you need to do
for matchers defined by implementing `MatcherInterface`):
```
// New matcher definition that works with the latest Google Mock.
using ::testing::MakePolymorphicMatcher;
using ::testing::MatchResultListener;
...
class MyGreatMatcher {
 public:
  ...
  bool MatchAndExplain(MyType value,
                       MatchResultListener* listener) const {
    // Returns true if value matches.
    return value.GetBar() < 42;
  }
  ...
};
... MakePolymorphicMatcher(MyGreatMatcher()) ...
```

If your polymorphic matcher uses `ExplainMatchResultTo()` for better
failure messages:
```
// Old matcher definition that doesn't work with the latest
// Google Mock.
using ::testing::MakePolymorphicMatcher;
...
class MyGreatMatcher {
 public:
  ...
  bool Matches(MyType value) const {
    // Returns true if value matches.
    return value.GetBar() < 42;
  }
  ...
};
void ExplainMatchResultTo(const MyGreatMatcher& matcher,
                          MyType value,
                          ::std::ostream* os) {
  // Prints some helpful information to os to help
  // a user understand why value matches (or doesn't match).
  *os << "the Bar property is " << value.GetBar();
}
... MakePolymorphicMatcher(MyGreatMatcher()) ...
```

you'll need to move the logic inside `ExplainMatchResultTo()` to
`MatchAndExplain()`:
```
// New matcher definition that works with the latest Google Mock.
using ::testing::MakePolymorphicMatcher;
using ::testing::MatchResultListener;
...
class MyGreatMatcher {
 public:
  ...
  bool MatchAndExplain(MyType value,
                       MatchResultListener* listener) const {
    // Returns true if value matches.
    *listener << "the Bar property is " << value.GetBar();
    return value.GetBar() < 42;
  }
  ...
};
... MakePolymorphicMatcher(MyGreatMatcher()) ...
```

For more information, you can read these
[two](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Writing_New_Monomorphic_Matchers)
[recipes](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Writing_New_Polymorphic_Matchers)
from the cookbook.  As always, you
are welcome to post questions on `googlemock@googlegroups.com` if you
need any help.

## When using Google Mock, do I have to use Google Test as the testing framework?  I have my favorite testing framework and don't want to switch. ##

Google Mock works out of the box with Google Test.  However, it's easy
to configure it to work with any testing framework of your choice.
[Here](http://code.google.com/p/googlemock/wiki/V1_7_ForDummies#Using_Google_Mock_with_Any_Testing_Framework) is how.

## How am I supposed to make sense of these horrible template errors? ##

If you are confused by the compiler errors gcc threw at you,
try consulting the _Google Mock Doctor_ tool first.  What it does is to
scan stdin for gcc error messages, and spit out diagnoses on the
problems (we call them diseases) your code has.

To "install", run command:
```
alias gmd='<path to googlemock>/scripts/gmock_doctor.py'
```

To use it, do:
```
<your-favorite-build-command> <your-test> 2>&1 | gmd
```

For example:
```
make my_test 2>&1 | gmd
```

Or you can run `gmd` and copy-n-paste gcc's error messages to it.

## Can I mock a variadic function? ##

You cannot mock a variadic function (i.e. a function taking ellipsis
(`...`) arguments) directly in Google Mock.

The problem is that in general, there is _no way_ for a mock object to
know how many arguments are passed to the variadic method, and what
the arguments' types are.  Only the _author of the base class_ knows
the protocol, and we cannot look into his head.

Therefore, to mock such a function, the _user_ must teach the mock
object how to figure out the number of arguments and their types.  One
way to do it is to provide overloaded versions of the function.

Ellipsis arguments are inherited from C and not really a C++ feature.
They are unsafe to use and don't work with arguments that have
constructors or destructors.  Therefore we recommend to avoid them in
C++ as much as possible.

## MSVC gives me warning C4301 or C4373 when I define a mock method with a const parameter.  Why? ##

If you compile this using Microsoft Visual C++ 2005 SP1:
```
class Foo {
  ...
  virtual void Bar(const int i) = 0;
};

class MockFoo : public Foo {
  ...
  MOCK_METHOD1(Bar, void(const int i));
};
```
You may get the following warning:
```
warning C4301: 'MockFoo::Bar': overriding virtual function only differs from 'Foo::Bar' by const/volatile qualifier
```

This is a MSVC bug.  The same code compiles fine with gcc ,for
example.  If you use Visual C++ 2008 SP1, you would get the warning:
```
warning C4373: 'MockFoo::Bar': virtual function overrides 'Foo::Bar', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers
```

In C++, if you _declare_ a function with a `const` parameter, the
`const` modifier is _ignored_.  Therefore, the `Foo` base class above
is equivalent to:
```
class Foo {
  ...
  virtual void Bar(int i) = 0;  // int or const int?  Makes no difference.
};
```

In fact, you can _declare_ Bar() with an `int` parameter, and _define_
it with a `const int` parameter.  The compiler will still match them
up.

Since making a parameter `const` is meaningless in the method
_declaration_, we recommend to remove it in both `Foo` and `MockFoo`.
That should workaround the VC bug.

Note that we are talking about the _top-level_ `const` modifier here.
If the function parameter is passed by pointer or reference, declaring
the _pointee_ or _referee_ as `const` is still meaningful.  For
example, the following two declarations are _not_ equivalent:
```
void Bar(int* p);        // Neither p nor *p is const.
void Bar(const int* p);  // p is not const, but *p is.
```

## I have a huge mock class, and Microsoft Visual C++ runs out of memory when compiling it.  What can I do? ##

We've noticed that when the `/clr` compiler flag is used, Visual C++
uses 5~6 times as much memory when compiling a mock class.  We suggest
to avoid `/clr` when compiling native C++ mocks.

## I can't figure out why Google Mock thinks my expectations are not satisfied.  What should I do? ##

You might want to run your test with
`--gmock_verbose=info`.  This flag lets Google Mock print a trace
of every mock function call it receives.  By studying the trace,
you'll gain insights on why the expectations you set are not met.

## How can I assert that a function is NEVER called? ##

```
EXPECT_CALL(foo, Bar(_))
    .Times(0);
```

## I have a failed test where Google Mock tells me TWICE that a particular expectation is not satisfied.  Isn't this redundant? ##

When Google Mock detects a failure, it prints relevant information
(the mock function arguments, the state of relevant expectations, and
etc) to help the user debug.  If another failure is detected, Google
Mock will do the same, including printing the state of relevant
expectations.

Sometimes an expectation's state didn't change between two failures,
and you'll see the same description of the state twice.  They are
however _not_ redundant, as they refer to _different points in time_.
The fact they are the same _is_ interesting information.

## I get a heap check failure when using a mock object, but using a real object is fine.  What can be wrong? ##

Does the class (hopefully a pure interface) you are mocking have a
virtual destructor?

Whenever you derive from a base class, make sure its destructor is
virtual.  Otherwise Bad Things will happen.  Consider the following
code:

```
class Base {
 public:
  // Not virtual, but should be.
  ~Base() { ... }
  ...
};

class Derived : public Base {
 public:
  ...
 private:
  std::string value_;
};

...
  Base* p = new Derived;
  ...
  delete p;  // Surprise! ~Base() will be called, but ~Derived() will not
             // - value_ is leaked.
```

By changing `~Base()` to virtual, `~Derived()` will be correctly
called when `delete p` is executed, and the heap checker
will be happy.

## The "newer expectations override older ones" rule makes writing expectations awkward.  Why does Google Mock do that? ##

When people complain about this, often they are referring to code like:

```
// foo.Bar() should be called twice, return 1 the first time, and return
// 2 the second time.  However, I have to write the expectations in the
// reverse order.  This sucks big time!!!
EXPECT_CALL(foo, Bar())
    .WillOnce(Return(2))
    .RetiresOnSaturation();
EXPECT_CALL(foo, Bar())
    .WillOnce(Return(1))
    .RetiresOnSaturation();
```

The problem is that they didn't pick the **best** way to express the test's
intent.

By default, expectations don't have to be matched in _any_ particular
order.  If you want them to match in a certain order, you need to be
explicit.  This is Google Mock's (and jMock's) fundamental philosophy: it's
easy to accidentally over-specify your tests, and we want to make it
harder to do so.

There are two better ways to write the test spec.  You could either
put the expectations in sequence:

```
// foo.Bar() should be called twice, return 1 the first time, and return
// 2 the second time.  Using a sequence, we can write the expectations
// in their natural order.
{
  InSequence s;
  EXPECT_CALL(foo, Bar())
      .WillOnce(Return(1))
      .RetiresOnSaturation();
  EXPECT_CALL(foo, Bar())
      .WillOnce(Return(2))
      .RetiresOnSaturation();
}
```

or you can put the sequence of actions in the same expectation:

```
// foo.Bar() should be called twice, return 1 the first time, and return
// 2 the second time.
EXPECT_CALL(foo, Bar())
    .WillOnce(Return(1))
    .WillOnce(Return(2))
    .RetiresOnSaturation();
```

Back to the original questions: why does Google Mock search the
expectations (and `ON_CALL`s) from back to front?  Because this
allows a user to set up a mock's behavior for the common case early
(e.g. in the mock's constructor or the test fixture's set-up phase)
and customize it with more specific rules later.  If Google Mock
searches from front to back, this very useful pattern won't be
possible.

## Google Mock prints a warning when a function without EXPECT\_CALL is called, even if I have set its behavior using ON\_CALL.  Would it be reasonable not to show the warning in this case? ##

When choosing between being neat and being safe, we lean toward the
latter.  So the answer is that we think it's better to show the
warning.

Often people write `ON_CALL`s in the mock object's
constructor or `SetUp()`, as the default behavior rarely changes from
test to test.  Then in the test body they set the expectations, which
are often different for each test.  Having an `ON_CALL` in the set-up
part of a test doesn't mean that the calls are expected.  If there's
no `EXPECT_CALL` and the method is called, it's possibly an error.  If
we quietly let the call go through without notifying the user, bugs
may creep in unnoticed.

If, however, you are sure that the calls are OK, you can write

```
EXPECT_CALL(foo, Bar(_))
    .WillRepeatedly(...);
```

instead of

```
ON_CALL(foo, Bar(_))
    .WillByDefault(...);
```

This tells Google Mock that you do expect the calls and no warning should be
printed.

Also, you can control the verbosity using the `--gmock_verbose` flag.
If you find the output too noisy when debugging, just choose a less
verbose level.

## How can I delete the mock function's argument in an action? ##

If you find yourself needing to perform some action that's not
supported by Google Mock directly, remember that you can define your own
actions using
[MakeAction()](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Writing_New_Actions) or
[MakePolymorphicAction()](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Writing_New_Polymorphic_Actions),
or you can write a stub function and invoke it using
[Invoke()](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Using_Functions_Methods_Functors).

## MOCK\_METHODn()'s second argument looks funny.  Why don't you use the MOCK\_METHODn(Method, return\_type, arg\_1, ..., arg\_n) syntax? ##

What?!  I think it's beautiful. :-)

While which syntax looks more natural is a subjective matter to some
extent, Google Mock's syntax was chosen for several practical advantages it
has.

Try to mock a function that takes a map as an argument:
```
virtual int GetSize(const map<int, std::string>& m);
```

Using the proposed syntax, it would be:
```
MOCK_METHOD1(GetSize, int, const map<int, std::string>& m);
```

Guess what?  You'll get a compiler error as the compiler thinks that
`const map<int, std::string>& m` are **two**, not one, arguments. To work
around this you can use `typedef` to give the map type a name, but
that gets in the way of your work.  Google Mock's syntax avoids this
problem as the function's argument types are protected inside a pair
of parentheses:
```
// This compiles fine.
MOCK_METHOD1(GetSize, int(const map<int, std::string>& m));
```

You still need a `typedef` if the return type contains an unprotected
comma, but that's much rarer.

Other advantages include:
  1. `MOCK_METHOD1(Foo, int, bool)` can leave a reader wonder whether the method returns `int` or `bool`, while there won't be such confusion using Google Mock's syntax.
  1. The way Google Mock describes a function type is nothing new, although many people may not be familiar with it.  The same syntax was used in C, and the `function` library in `tr1` uses this syntax extensively.  Since `tr1` will become a part of the new version of STL, we feel very comfortable to be consistent with it.
  1. The function type syntax is also used in other parts of Google Mock's API (e.g. the action interface) in order to make the implementation tractable. A user needs to learn it anyway in order to utilize Google Mock's more advanced features.  We'd as well stick to the same syntax in `MOCK_METHOD*`!

## My code calls a static/global function.  Can I mock it? ##

You can, but you need to make some changes.

In general, if you find yourself needing to mock a static function,
it's a sign that your modules are too tightly coupled (and less
flexible, less reusable, less testable, etc).  You are probably better
off defining a small interface and call the function through that
interface, which then can be easily mocked.  It's a bit of work
initially, but usually pays for itself quickly.

This Google Testing Blog
[post](http://googletesting.blogspot.com/2008/06/defeat-static-cling.html)
says it excellently.  Check it out.

## My mock object needs to do complex stuff.  It's a lot of pain to specify the actions.  Google Mock sucks! ##

I know it's not a question, but you get an answer for free any way. :-)

With Google Mock, you can create mocks in C++ easily.  And people might be
tempted to use them everywhere. Sometimes they work great, and
sometimes you may find them, well, a pain to use. So, what's wrong in
the latter case?

When you write a test without using mocks, you exercise the code and
assert that it returns the correct value or that the system is in an
expected state.  This is sometimes called "state-based testing".

Mocks are great for what some call "interaction-based" testing:
instead of checking the system state at the very end, mock objects
verify that they are invoked the right way and report an error as soon
as it arises, giving you a handle on the precise context in which the
error was triggered.  This is often more effective and economical to
do than state-based testing.

If you are doing state-based testing and using a test double just to
simulate the real object, you are probably better off using a fake.
Using a mock in this case causes pain, as it's not a strong point for
mocks to perform complex actions.  If you experience this and think
that mocks suck, you are just not using the right tool for your
problem. Or, you might be trying to solve the wrong problem. :-)

## I got a warning "Uninteresting function call encountered - default action taken.."  Should I panic? ##

By all means, NO!  It's just an FYI.

What it means is that you have a mock function, you haven't set any
expectations on it (by Google Mock's rule this means that you are not
interested in calls to this function and therefore it can be called
any number of times), and it is called.  That's OK - you didn't say
it's not OK to call the function!

What if you actually meant to disallow this function to be called, but
forgot to write `EXPECT_CALL(foo, Bar()).Times(0)`?  While
one can argue that it's the user's fault, Google Mock tries to be nice and
prints you a note.

So, when you see the message and believe that there shouldn't be any
uninteresting calls, you should investigate what's going on.  To make
your life easier, Google Mock prints the function name and arguments
when an uninteresting call is encountered.

## I want to define a custom action.  Should I use Invoke() or implement the action interface? ##

Either way is fine - you want to choose the one that's more convenient
for your circumstance.

Usually, if your action is for a particular function type, defining it
using `Invoke()` should be easier; if your action can be used in
functions of different types (e.g. if you are defining
`Return(value)`), `MakePolymorphicAction()` is
easiest.  Sometimes you want precise control on what types of
functions the action can be used in, and implementing
`ActionInterface` is the way to go here. See the implementation of
`Return()` in `include/gmock/gmock-actions.h` for an example.

## I'm using the set-argument-pointee action, and the compiler complains about "conflicting return type specified".  What does it mean? ##

You got this error as Google Mock has no idea what value it should return
when the mock method is called.  `SetArgPointee()` says what the
side effect is, but doesn't say what the return value should be.  You
need `DoAll()` to chain a `SetArgPointee()` with a `Return()`.

See this [recipe](http://code.google.com/p/googlemock/wiki/V1_7_CookBook#Mocking_Side_Effects) for more details and an example.


## My question is not in your FAQ! ##

If you cannot find the answer to your question in this FAQ, there are
some other resources you can use:

  1. read other [wiki pages](http://code.google.com/p/googlemock/w/list),
  1. search the mailing list [archive](http://groups.google.com/group/googlemock/topics),
  1. ask it on [googlemock@googlegroups.com](mailto:googlemock@googlegroups.com) and someone will answer it (to prevent spam, we require you to join the [discussion group](http://groups.google.com/group/googlemock) before you can post.).

Please note that creating an issue in the
[issue tracker](http://code.google.com/p/googlemock/issues/list) is _not_
a good way to get your answer, as it is monitored infrequently by a
very small number of people.

When asking a question, it's helpful to provide as much of the
following information as possible (people cannot help you if there's
not enough information in your question):

  * the version (or the revision number if you check out from SVN directly) of Google Mock you use (Google Mock is under active development, so it's possible that your problem has been solved in a later version),
  * your operating system,
  * the name and version of your compiler,
  * the complete command line flags you give to your compiler,
  * the complete compiler error messages (if the question is about compilation),
  * the _actual_ code (ideally, a minimal but complete program) that has the problem you encounter.
perty */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/////////////////////////////////////////////////////////////////////////
// $Id: floppy.cc,v 1.69 2003/12/18 20:04:49 vruppert Exp $
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2002  MandrakeSoft S.A.
//
//    MandrakeSoft S.A.
//    43, rue d'Aboukir
//    75002 Paris - France
//    http://www.linux-mandrake.com/
//    http://www.mandrakesoft.com/
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library 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
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
//
//
// Floppy Disk Controller Docs:
// Intel 82077A Data sheet
//   ftp://void-core.2y.net/pub/docs/fdc/82077AA_FloppyControllerDatasheet.pdf
// Intel 82078 Data sheet
//   ftp://download.intel.com/design/periphrl/datashts/29047403.PDF
// Other FDC references
//   http://debs.future.easyspace.com/Programming/Hardware/FDC/floppy.html
// And a port list:
//   http://mudlist.eorbit.net/~adam/pickey/ports.html
//

// Define BX_PLUGGABLE in files that can be compiled into plugins.  For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE 
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE


extern "C" {
#include <errno.h>
}

#ifdef __linux__
extern "C" {
#include <sys/ioctl.h>
#include <linux/fd.h>
}
#endif
#include "bochs.h"
// windows.h included by bochs.h
#ifdef WIN32
extern "C" {
#include <winioctl.h>
}
#endif
#define LOG_THIS theFloppyController->

bx_floppy_ctrl_c *theFloppyController;

/* for main status register */
#define FD_MS_MRQ  0x80
#define FD_MS_DIO  0x40
#define FD_MS_NDMA 0x20
#define FD_MS_BUSY 0x10
#define FD_MS_ACTD 0x08
#define FD_MS_ACTC 0x04
#define FD_MS_ACTB 0x02
#define FD_MS_ACTA 0x01

#define FROM_FLOPPY 10
#define TO_FLOPPY   11

#define FLOPPY_DMA_CHAN 2

typedef struct {
  unsigned id;
  Bit8u trk;
  Bit8u hd;
  Bit8u spt;
  unsigned sectors;
} floppy_type_t;

static floppy_type_t floppy_type[8] = {
  {BX_FLOPPY_160K, 40, 1, 8, 320},
  {BX_FLOPPY_180K, 40, 1, 9, 360},
  {BX_FLOPPY_320K, 40, 2, 8, 640},
  {BX_FLOPPY_360K, 40, 2, 9, 720},
  {BX_FLOPPY_720K, 80, 2, 9, 1440},
  {BX_FLOPPY_1_2,  80, 2, 15, 2400},
  {BX_FLOPPY_1_44, 80, 2, 18, 2880},
  {BX_FLOPPY_2_88, 80, 2, 36, 5760}
};


  int
libfloppy_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[])
{
  theFloppyController = new bx_floppy_ctrl_c ();
  bx_devices.pluginFloppyDevice = theFloppyController;
  BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theFloppyController, BX_PLUGIN_FLOPPY);
  return(0); // Success
}

  void
libfloppy_LTX_plugin_fini(void)
{
}


bx_floppy_ctrl_c::bx_floppy_ctrl_c(void)
{
  put("FDD");
  settype(FDLOG);
  s.floppy_timer_index = BX_NULL_TIMER_HANDLE;
}

bx_floppy_ctrl_c::~bx_floppy_ctrl_c(void)
{
  // nothing for now
  BX_DEBUG(("Exit."));
}


  void
bx_floppy_ctrl_c::init(void)
{
  Bit8u i;

  BX_DEBUG(("Init $Id: floppy.cc,v 1.69 2003/12/18 20:04:49 vruppert Exp $"));
  DEV_dma_register_8bit_channel(2, dma_read, dma_write, "Floppy Drive");
  DEV_register_irq(6, "Floppy Drive");
  for (unsigned addr=0x03F2; addr<=0x03F7; addr++) {
    DEV_register_ioread_handler(this, read_handler, addr, "Floppy Drive", 1);
    DEV_register_iowrite_handler(this, write_handler, addr, "Floppy Drive", 1);
    }


  DEV_cmos_set_reg(0x10, 0x00); /* start out with: no drive 0, no drive 1 */

  BX_FD_THIS s.num_supported_floppies = 0;

  for (i=0; i<4; i++) {
    BX_FD_THIS s.device_type[i] = BX_FLOPPY_NONE;
    BX_FD_THIS s.media[i].type = BX_FLOPPY_NONE;
    }

  //
  // Floppy A setup
  //
  BX_FD_THIS s.media[0].sectors_per_track = 0;
  BX_FD_THIS s.media[0].tracks            = 0;
  BX_FD_THIS s.media[0].heads             = 0;
  BX_FD_THIS s.media[0].sectors           = 0;
  BX_FD_THIS s.media[0].fd = -1;
  BX_FD_THIS s.media_present[0] = 0;
  BX_FD_THIS s.device_type[0] = bx_options.floppya.Odevtype->get ();

  switch (BX_FD_THIS s.device_type[0]) {
    case BX_FLOPPY_NONE:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x00);
      break;
    case BX_FLOPPY_360K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x10);
      break;
    case BX_FLOPPY_1_2:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x20);
      break;
    case BX_FLOPPY_720K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x30);
      break;
    case BX_FLOPPY_1_44:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x40);
      break;
    case BX_FLOPPY_2_88:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x50);
      break;

    // use CMOS reserved types
    case BX_FLOPPY_160K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x60);
      BX_INFO(("WARNING: 1st floppy uses of reserved CMOS floppy drive type 6"));
      break;
    case BX_FLOPPY_180K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x70);
      BX_INFO(("WARNING: 1st floppy uses of reserved CMOS floppy drive type 7"));
      break;
    case BX_FLOPPY_320K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0x0f) | 0x80);
      BX_INFO(("WARNING: 1st floppy uses of reserved CMOS floppy drive type 8"));
      break;

    default:
      BX_PANIC(("unknown floppya type"));
    }
  if (BX_FD_THIS s.device_type[0] != BX_FLOPPY_NONE)
    BX_FD_THIS s.num_supported_floppies++;

  if (bx_options.floppya.Otype->get () != BX_FLOPPY_NONE) {
    if ( bx_options.floppya.Ostatus->get () == BX_INSERTED) {
      if (evaluate_media(bx_options.floppya.Otype->get (), bx_options.floppya.Opath->getptr (),
                   & BX_FD_THIS s.media[0]))
        BX_FD_THIS s.media_present[0] = 1;
      else
        bx_options.floppya.Ostatus->set(BX_EJECTED);
#define MED (BX_FD_THIS s.media[0])
        BX_INFO(("fd0: '%s' ro=%d, h=%d,t=%d,spt=%d", bx_options.floppya.Opath->getptr(),
        MED.write_protected, MED.heads, MED.tracks, MED.sectors_per_track));
#undef MED
      }
    }


  //
  // Floppy B setup
  //
  BX_FD_THIS s.media[1].sectors_per_track = 0;
  BX_FD_THIS s.media[1].tracks            = 0;
  BX_FD_THIS s.media[1].heads             = 0;
  BX_FD_THIS s.media[1].sectors           = 0;
  BX_FD_THIS s.media[1].fd = -1;
  BX_FD_THIS s.media_present[1] = 0;
  BX_FD_THIS s.device_type[1] = bx_options.floppyb.Odevtype->get ();

  switch (BX_FD_THIS s.device_type[1]) {
    case BX_FLOPPY_NONE:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x00);
      break;
    case BX_FLOPPY_360K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x01);
      break;
    case BX_FLOPPY_1_2:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x02);
      break;
    case BX_FLOPPY_720K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x03);
      break;
    case BX_FLOPPY_1_44:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x04);
      break;
    case BX_FLOPPY_2_88:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x05);
      break;

    // use CMOS reserved types
    case BX_FLOPPY_160K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x06);
      BX_INFO(("WARNING: 2nd floppy uses of reserved CMOS floppy drive type 6"));
      break;
    case BX_FLOPPY_180K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x07);
      BX_INFO(("WARNING: 2nd floppy uses of reserved CMOS floppy drive type 7"));
      break;
    case BX_FLOPPY_320K:
      DEV_cmos_set_reg(0x10, (DEV_cmos_get_reg(0x10) & 0xf0) | 0x08);
      BX_INFO(("WARNING: 2nd floppy uses of reserved CMOS floppy drive type 8"));
      break;

    default:
      BX_PANIC(("unknown floppyb type"));
    }
  if (BX_FD_THIS s.device_type[1] != BX_FLOPPY_NONE)
    BX_FD_THIS s.num_supported_floppies++;

  if (bx_options.floppyb.Otype->get () != BX_FLOPPY_NONE) {
    if ( bx_options.floppyb.Ostatus->get () == BX_INSERTED) {
      if (evaluate_media(bx_options.floppyb.Otype->get (), bx_options.floppyb.Opath->getptr (),
                   & BX_FD_THIS s.media[1]))
        BX_FD_THIS s.media_present[1] = 1;
      else
        bx_options.floppyb.Ostatus->set(BX_EJECTED);
#define MED (BX_FD_THIS s.media[1])
        BX_INFO(("fd1: '%s' ro=%d, h=%d,t=%d,spt=%d", bx_options.floppyb.Opath->getptr(),
        MED.write_protected, MED.heads, MED.tracks, MED.sectors_per_track));
#undef MED
      }
    }



  /* CMOS Equipment Byte register */
  if (BX_FD_THIS s.num_supported_floppies > 0) {
    DEV_cmos_set_reg(0x14, (DEV_cmos_get_reg(0x14) & 0x3e) |
                          ((BX_FD_THIS s.num_supported_floppies-1) << 6) | 1);
  }
  else
    DEV_cmos_set_reg(0x14, (DEV_cmos_get_reg(0x14) & 0x3e));


  if (BX_FD_THIS s.floppy_timer_index == BX_NULL_TIMER_HANDLE) {
    BX_FD_THIS s.floppy_timer_index =
      bx_pc_system.register_timer( this, timer_handler,
      bx_options.Ofloppy_command_delay->get (), 0,0, "floppy");
  }

  BX_DEBUG(("bx_options.Ofloppy_command_delay = %u",
    (unsigned) bx_options.Ofloppy_command_delay->get ()));
}



  void
bx_floppy_ctrl_c::reset(unsigned type)
{
  Bit32u i;

  BX_FD_THIS s.pending_irq = 0;
  BX_FD_THIS s.reset_sensei = 0; /* no reset result present */

  BX_FD_THIS s.main_status_reg = 0;
  BX_FD_THIS s.status_reg0 = 0;
  BX_FD_THIS s.status_reg1 = 0;
  BX_FD_THIS s.status_reg2 = 0;
  BX_FD_THIS s.status_reg3 = 0;

  // software reset (via DOR port 0x3f2 bit 2) does not change DOR
  if (type == BX_RESET_HARDWARE) {
    BX_FD_THIS s.DOR = 0x0c;
    // motor off, drive 3..0
    // DMA/INT enabled
    // normal operation
    // drive select 0

    // DIR and CCR affected only by hard reset
    for (i=0; i<4; i++) {
      BX_FD_THIS s.DIR[i] |= 0x80; // disk changed
      }
    BX_FD_THIS s.data_rate = 0; /* 500 Kbps */
    }

  for (i=0; i<4; i++) {
    BX_FD_THIS s.cylinder[i] = 0;
    BX_FD_THIS s.head[i] = 0;
    BX_FD_THIS s.sector[i] = 0;
    }

  DEV_pic_lower_irq(6);
  DEV_dma_set_drq(FLOPPY_DMA_CHAN, 0);
  enter_idle_phase();
}


  // static IO port read callback handler
  // redirects to non-static class handler to avoid virtual functions

  Bit32u
bx_floppy_ctrl_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_FD_SMF
  bx_floppy_ctrl_c *class_ptr = (bx_floppy_ctrl_c *) this_ptr;

  return( class_ptr->read(address, io_len) );
}


  /* reads from the floppy io ports */
  Bit32u
bx_floppy_ctrl_c::read(Bit32u address, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_FD_SMF
  Bit8u status, value;

  if (bx_dbg.floppy)
    BX_INFO(("read access to port %04x", (unsigned) address));

  switch (address) {
#if BX_DMA_FLOPPY_IO
    case 0x3F2: // diskette controller digital output register
      value = BX_FD_THIS s.DOR;
      return(value);
      break;

    case 0x3F4: /* diskette controller main status register */
      status = BX_FD_THIS s.main_status_reg;
      return(status);
      break;

    case 0x3F5: /* diskette controller data */
      if (BX_FD_THIS s.result_size == 0) {
        BX_ERROR(("port 0x3f5: no results to read"));
        BX_FD_THIS s.main_status_reg = 0;
        return BX_FD_THIS s.result[0];
        }

      value = BX_FD_THIS s.result[BX_FD_THIS s.result_index++];
      BX_FD_THIS s.main_status_reg &= 0xF0;
      if (BX_FD_THIS s.result_index >= BX_FD_THIS s.result_size) {
        if (!BX_FD_THIS s.reset_sensei) BX_FD_THIS s.pending_irq = 0;
        DEV_pic_lower_irq(6);
        enter_idle_phase();
        }
      return(value);
      break;
#endif  // #if BX_DMA_FLOPPY_IO

    case 0x3F3: // Tape Drive Register
      // see http://www.smsc.com/main/datasheets/37c93x.pdf page 18 for more details

      switch( BX_FD_THIS s.DOR & 0x03 )
      {
        case 0x00:
          if( (BX_FD_THIS s.DOR & 0x10) == 0) break;
          return(2);
        case 0x01:
          if( (BX_FD_THIS s.DOR & 0x20) == 0) break;
          return(1);
      }
      return(3);
      
    case 0x3F6: // Reserved for future floppy controllers
                // This address shared with the hard drive controller
      value = DEV_hd_read_handler(bx_devices.pluginHardDrive, address, io_len);
      return( value );
      break;

    case 0x3F7: // diskette controller digital input register
      // This address shared with the hard drive controller:
      //   Bit  7   : floppy
      //   Bits 6..0: hard drive
      value = DEV_hd_read_handler(bx_devices.pluginHardDrive, address, io_len);
      value &= 0x7f;
      // add in diskette change line
      value |= (BX_FD_THIS s.DIR[BX_FD_THIS s.DOR & 0x03] & 0x80);
      return( value );
      break;
    default:
      BX_ERROR(("io_read: unsupported address 0x%04x", (unsigned) address));
      return(0);
      break;
    }
}


  // static IO port write callback handler
  // redirects to non-static class handler to avoid virtual functions

  void
bx_floppy_ctrl_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
{
#if !BX_USE_FD_SMF
  bx_floppy_ctrl_c *class_ptr = (bx_floppy_ctrl_c *) this_ptr;

  class_ptr->write(address, value, io_len);
}

  /* writes to the floppy io ports */
  void
bx_floppy_ctrl_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_FD_SMF
  Bit8u dma_and_interrupt_enable;
  Bit8u normal_operation, prev_normal_operation;
  Bit8u drive_select;
  Bit8u motor_on_drive0, motor_on_drive1;

  if (bx_dbg.floppy)
    BX_INFO(("write access to port %04x, value=%02x",
      (unsigned) address, (unsigned) value));

  switch (address) {
#if BX_DMA_FLOPPY_IO
    case 0x3F2: /* diskette controller digital output register */
      motor_on_drive1 = value & 0x20;
      motor_on_drive0 = value & 0x10;
      dma_and_interrupt_enable = value & 0x08;
      if (!dma_and_interrupt_enable)
        BX_DEBUG(("DMA and interrupt capabilities disabled"));
      normal_operation = value & 0x04;
      drive_select = value & 0x03;

      prev_normal_operation = BX_FD_THIS s.DOR & 0x04;
      BX_FD_THIS s.DOR = value;

      if (prev_normal_operation==0 && normal_operation) {
        // transition from RESET to NORMAL
        bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
             bx_options.Ofloppy_command_delay->get (), 0 );
        }
      else if (prev_normal_operation && normal_operation==0) {
        // transition from NORMAL to RESET
        BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
        BX_FD_THIS s.pending_command = 0xfe; // RESET pending

        }
        BX_DEBUG(("io_write: digital output register"));
        BX_DEBUG(("  motor on, drive1 = %d", motor_on_drive1 > 0));
        BX_DEBUG(("  motor on, drive0 = %d", motor_on_drive0 > 0));
        BX_DEBUG(("  dma_and_interrupt_enable=%02x",
          (unsigned) dma_and_interrupt_enable));
        BX_DEBUG(("  normal_operation=%02x",
          (unsigned) normal_operation));
        BX_DEBUG(("  drive_select=%02x",
          (unsigned) drive_select));
      if (BX_FD_THIS s.device_type[drive_select] == BX_FLOPPY_NONE) {
        BX_DEBUG(("WARNING: not existing drive selected"));
        }
      break;

    case 0x3f4: /* diskette controller data rate select register */
      BX_ERROR(("io_write: data rate select register unsupported"));
      break;

    case 0x3F5: /* diskette controller data */
      BX_DEBUG(("command = %02x", (unsigned) value));
      if (BX_FD_THIS s.command_complete) {
        if (BX_FD_THIS s.pending_command!=0)
          BX_PANIC(("io: 3f5: receiving new comm, old one (%02x) pending",
            (unsigned) BX_FD_THIS s.pending_command));
        BX_FD_THIS s.command[0] = value;
        BX_FD_THIS s.command_complete = 0;
        BX_FD_THIS s.command_index = 1;
        /* read/write command in progress */
        BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_BUSY;
        switch (value) {
          case 0x03: /* specify */
            BX_FD_THIS s.command_size = 3;
            break;
          case 0x04: // get status
            BX_FD_THIS s.command_size = 2;
            break;
          case 0x07: /* recalibrate */
            BX_FD_THIS s.command_size = 2;
            break;
          case 0x08: /* sense interrupt status */
            BX_FD_THIS s.command_size = 1;
            break;
          case 0x0f: /* seek */
            BX_FD_THIS s.command_size = 3;
            break;
          case 0x4a: /* read ID */
            BX_FD_THIS s.command_size = 2;
            break;
          case 0x4d: /* format track */
            BX_FD_THIS s.command_size = 6;
            break;
          case 0x45:
          case 0xc5: /* write normal data */
            BX_FD_THIS s.command_size = 9;
            break;
          case 0x46:
          case 0x66:
          case 0xc6:
          case 0xe6: /* read normal data */
            BX_FD_THIS s.command_size = 9;
            break;

          case 0x13: // Configure command (Enhanced)
            BX_FD_THIS s.command_size = 4;
            break;

          case 0x0e: // dump registers (Enhanced drives)
          case 0x10: // Version command, standard controller returns 80h
          case 0x18: // National Semiconductor version command; return 80h
            // These commands are not implemented on the standard
            // controller and return an error.  They are available on
            // the enhanced controller.
            BX_DEBUG(("io_write: 0x3f5: unsupported floppy command 0x%02x",
              (unsigned) value));
            BX_FD_THIS s.command_size = 0;   // make sure we don't try to process this command
            BX_FD_THIS s.status_reg0 = 0x80; // status: invalid command
            enter_result_phase();
            break;

          default:
            BX_ERROR(("io_write: 0x3f5: invalid floppy command 0x%02x",
              (unsigned) value));
            BX_FD_THIS s.command_size = 0;   // make sure we don't try to process this command
            BX_FD_THIS s.status_reg0 = 0x80; // status: invalid command
            enter_result_phase();
            break;
          }
        }
      else {
        BX_FD_THIS s.command[BX_FD_THIS s.command_index++] =
          value;
        }
      if (BX_FD_THIS s.command_index ==
        BX_FD_THIS s.command_size) {
        /* read/write command not in progress any more */
        floppy_command();
        BX_FD_THIS s.command_complete = 1;
        }
      BX_DEBUG(("io_write: diskette controller data"));
      return;
      break;
#endif  // #if BX_DMA_FLOPPY_IO

    case 0x3F6: /* diskette controller (reserved) */
      BX_DEBUG(("io_write: reserved register 0x3f6 unsupported"));
      // this address shared with the hard drive controller
      DEV_hd_write_handler(bx_devices.pluginHardDrive, address, value, io_len);
      break;

#if BX_DMA_FLOPPY_IO
    case 0x3F7: /* diskette controller configuration control register */
      BX_DEBUG(("io_write: config control register"));
      BX_FD_THIS s.data_rate = value & 0x03;
      switch (BX_FD_THIS s.data_rate) {
        case 0: BX_DEBUG(("  500 Kbps")); break;
        case 1: BX_DEBUG(("  300 Kbps")); break;
        case 2: BX_DEBUG(("  250 Kbps")); break;
        case 3: BX_DEBUG(("  1 Mbps")); break;
      }
      return;
      break;

   default:
      BX_ERROR(("io_write ignored: 0x%04x = 0x%02x", (unsigned) address, (unsigned) value));
      break;
#endif  // #if BX_DMA_FLOPPY_IO
    }
}



  void
bx_floppy_ctrl_c::floppy_command(void)
{
#if BX_PROVIDE_CPU_MEMORY==0
  BX_PANIC(("floppy_command(): uses DMA: not supported for"
           " external environment"));
#else
  unsigned i;
  Bit8u step_rate_time;
  Bit8u head_unload_time;
  Bit8u head_load_time;
  Bit8u motor_on;
  Bit8u head, drive, cylinder, sector, eot;
  Bit8u sector_size, data_length;
  Bit32u logical_sector;


  BX_DEBUG(("FLOPPY COMMAND: "));
  for (i=0; i<BX_FD_THIS s.command_size; i++)
    BX_DEBUG(("[%02x] ", (unsigned) BX_FD_THIS s.command[i]));

#if 0
  /* execute phase of command is in progress (non DMA mode) */
  BX_FD_THIS s.main_status_reg |= 20;
#endif

  BX_FD_THIS s.pending_command = BX_FD_THIS s.command[0];
  switch (BX_FD_THIS s.pending_command) {
    case 0x03: // specify
      // execution: specified parameters are loaded
      // result: no result bytes, no interrupt
      step_rate_time = BX_FD_THIS s.command[1] >> 4;
      head_unload_time = BX_FD_THIS s.command[1] & 0x0f;
      head_load_time = BX_FD_THIS s.command[2] >> 1;
      if (BX_FD_THIS s.command[2] & 0x01)
        BX_ERROR(("non DMA mode selected"));
      enter_idle_phase();
      return;
      break;

    case 0x04: // get status
      drive = (BX_FD_THIS s.command[1] & 0x03);
      BX_FD_THIS s.head[drive] = (BX_FD_THIS s.command[1] >> 2) & 0x01;
      BX_FD_THIS s.status_reg3 = 0x28 | (BX_FD_THIS s.head[drive]<<2) | drive
        | (BX_FD_THIS s.media[drive].write_protected ? 0x40 : 0x00);
      if (BX_FD_THIS s.cylinder[drive] == 0) BX_FD_THIS s.status_reg3 |= 0x10;
      enter_result_phase();
      return;
      break;

    case 0x07: // recalibrate
      drive = (BX_FD_THIS s.command[1] & 0x03);
      BX_FD_THIS s.DOR &= 0xfc;
      BX_FD_THIS s.DOR |= drive;
      BX_DEBUG(("floppy_command(): recalibrate drive %u",
        (unsigned) drive));
      motor_on = ( (BX_FD_THIS s.DOR>>(drive+4))
                     & 0x01 );
      if (motor_on == 0) {
        BX_INFO(("floppy_command(): recal drive with motor off"));
        }
      if (drive==0)
        BX_FD_THIS s.DOR |= 0x10; // turn on MOTA
      else
        BX_FD_THIS s.DOR |= 0x20; // turn on MOTB
      bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
        bx_options.Ofloppy_command_delay->get (), 0 );
      /* command head to track 0
       * controller set to non-busy
       * error condition noted in Status reg 0's equipment check bit
       * seek end bit set to 1 in Status reg 0 regardless of outcome
       * The last two are taken care of in timer().
       */
      BX_FD_THIS s.cylinder[drive] = 0;
      BX_FD_THIS s.main_status_reg = (1 << drive);
      return;
      break;

    case 0x08: /* sense interrupt status */
      /* execution:
       *   get status
       * result:
       *   no interupt
       *   byte0 = status reg0
       *   byte1 = current cylinder number (0 to 79)
       */
      drive = BX_FD_THIS s.DOR & 0x03;
      if (!BX_FD_THIS s.pending_irq) {
        BX_FD_THIS s.status_reg0 = 0x80;
        }
      else {
        if (BX_FD_THIS s.reset_sensei > 0) {
          drive = 4 - BX_FD_THIS s.reset_sensei;
          BX_FD_THIS s.status_reg0 &= 0xf8;
          BX_FD_THIS s.status_reg0 |= (BX_FD_THIS s.head[drive] << 2) | drive;
          BX_FD_THIS s.reset_sensei--;
          }
        }

      BX_DEBUG(("sense interrupt status"));
      enter_result_phase();
      return;
      break;

    case 0x0f: /* seek */
      /* command:
       *   byte0 = 0F
       *   byte1 = drive & head select
       *   byte2 = cylinder number
       * execution:
       *   postion head over specified cylinder
       * result:
       *   no result bytes, issues an interrupt
       */
      drive = BX_FD_THIS s.command[1] & 0x03;
      BX_FD_THIS s.DOR &= 0xfc;
      BX_FD_THIS s.DOR |= drive;

      BX_FD_THIS s.head[drive] = (BX_FD_THIS s.command[1] >> 2) & 0x01;
      BX_FD_THIS s.cylinder[drive] = BX_FD_THIS s.command[2];
      /* ??? should also check cylinder validity */
      bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
        bx_options.Ofloppy_command_delay->get (), 0 );
      /* data reg not ready, drive busy */
      BX_FD_THIS s.main_status_reg = (1 << drive);
      return;
      break;

    case 0x13: // Configure
      BX_DEBUG(("configure (eis     = 0x%02x)", BX_FD_THIS s.command[2] & 0x40 ));
      BX_DEBUG(("configure (efifo   = 0x%02x)", BX_FD_THIS s.command[2] & 0x20 ));
      BX_DEBUG(("configure (no poll = 0x%02x)", BX_FD_THIS s.command[2] & 0x10 ));
      BX_DEBUG(("configure (fifothr = 0x%02x)", BX_FD_THIS s.command[2] & 0x0f ));
      BX_DEBUG(("configure (pretrk  = 0x%02x)", BX_FD_THIS s.command[3] ));
      enter_idle_phase();
      return;
      break;

    case 0x4a: // read ID
      drive = BX_FD_THIS s.command[1] & 0x03;
      BX_FD_THIS s.head[drive] = (BX_FD_THIS s.command[1] >> 2) & 0x01;
      BX_FD_THIS s.DOR &= 0xfc;
      BX_FD_THIS s.DOR |= drive;

      motor_on = (BX_FD_THIS s.DOR>>(drive+4)) & 0x01;
      if (motor_on == 0) {
        BX_ERROR(("floppy_command(): 0x4a: motor not on"));
        BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
        return;
        }
      if (BX_FD_THIS s.device_type[drive] == BX_FLOPPY_NONE)
        BX_PANIC(("floppy_command(): read ID: bad drive #%d", drive));
      BX_FD_THIS s.status_reg0 = (BX_FD_THIS s.head[drive]<<2) | drive;
      bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
        bx_options.Ofloppy_command_delay->get (), 0 );
      /* data reg not ready, controller busy */
      BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
      return;
      break;

    case 0x4d: // format track
        drive = BX_FD_THIS s.command[1] & 0x03;
        BX_FD_THIS s.DOR &= 0xfc;
        BX_FD_THIS s.DOR |= drive;

        motor_on = (BX_FD_THIS s.DOR>>(drive+4)) & 0x01;
        if (motor_on == 0)
          BX_PANIC(("floppy_command(): format track: motor not on"));
        BX_FD_THIS s.head[drive] = (BX_FD_THIS s.command[1] >> 2) & 0x01;
        sector_size = BX_FD_THIS s.command[2];
        BX_FD_THIS s.format_count = BX_FD_THIS s.command[3];
        BX_FD_THIS s.format_fillbyte = BX_FD_THIS s.command[5];
        if (BX_FD_THIS s.device_type[drive] == BX_FLOPPY_NONE)
          BX_PANIC(("floppy_command(): format track: bad drive #%d", drive));

        if (sector_size != 0x02) { // 512 bytes
          BX_PANIC(("format track: sector size %d not supported", 128<<sector_size));
          }
        if (BX_FD_THIS s.format_count != BX_FD_THIS s.media[drive].sectors_per_track) {
          BX_PANIC(("format track: %d sectors/track requested (%d expected)",
                    BX_FD_THIS s.format_count, BX_FD_THIS s.media[drive].sectors_per_track));
          }
        if ( BX_FD_THIS s.media_present[drive] == 0 ) {
          // media not in drive, return error
          BX_INFO(("attempt to format track with media not present"));
          BX_FD_THIS s.status_reg0 = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive; // abnormal termination
          BX_FD_THIS s.status_reg1 = 0x25; // 0010 0101
          BX_FD_THIS s.status_reg2 = 0x31; // 0011 0001
          enter_result_phase();
          return;
          }
        if (BX_FD_THIS s.media[drive].write_protected) {
          // media write-protected, return error
          BX_INFO(("attempt to format track with media write-protected"));
          BX_FD_THIS s.status_reg0 = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive; // abnormal termination
          BX_FD_THIS s.status_reg1 = 0x27; // 0010 0111
          BX_FD_THIS s.status_reg2 = 0x31; // 0011 0001
          enter_result_phase();
          return;
          }

      /* 4 header bytes per sector are required */
      BX_FD_THIS s.format_count <<= 2;

      DEV_dma_set_drq(FLOPPY_DMA_CHAN, 1);

      /* data reg not ready, controller busy */
      BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
      BX_DEBUG(("format track"));
      return;
      break;

    case 0x46: // read normal data, MT=0, SK=0
    case 0x66: // read normal data, MT=0, SK=1
    case 0xc6: // read normal data, MT=1, SK=0
    case 0xe6: // read normal data, MT=1, SK=1
    case 0x45: // write normal data, MT=0
    case 0xc5: // write normal data, MT=1
      BX_FD_THIS s.multi_track = (BX_FD_THIS s.command[0] >> 7);
      if ( (BX_FD_THIS s.DOR & 0x08) == 0 )
        BX_PANIC(("read/write command with DMA and int disabled"));
      drive = BX_FD_THIS s.command[1] & 0x03;
      BX_FD_THIS s.DOR &= 0xfc;
      BX_FD_THIS s.DOR |= drive;

      motor_on = (BX_FD_THIS s.DOR>>(drive+4)) & 0x01;
      if (motor_on == 0)
        BX_PANIC(("floppy_command(): read/write: motor not on"));
      head = BX_FD_THIS s.command[3] & 0x01;
      cylinder = BX_FD_THIS s.command[2]; /* 0..79 depending */
      sector = BX_FD_THIS s.command[4];   /* 1..36 depending */
      eot = BX_FD_THIS s.command[6];      /* 1..36 depending */
      sector_size = BX_FD_THIS s.command[5];
      data_length = BX_FD_THIS s.command[8];
      BX_DEBUG(("read/write normal data"));
      BX_DEBUG(("BEFORE"));
      BX_DEBUG(("  drive    = %u", (unsigned) drive));
      BX_DEBUG(("  head     = %u", (unsigned) head));
      BX_DEBUG(("  cylinder = %u", (unsigned) cylinder));
      BX_DEBUG(("  sector   = %u", (unsigned) sector));
      BX_DEBUG(("  eot      = %u", (unsigned) eot));
      if (BX_FD_THIS s.device_type[drive] == BX_FLOPPY_NONE)
        BX_PANIC(("floppy_command(): read/write: bad drive #%d", drive));

      // check that head number in command[1] bit two matches the head
      // reported in the head number field.  Real floppy drives are
      // picky about this, as reported in SF bug #439945, (Floppy drive
      // read input error checking).
      if (head != ((BX_FD_THIS s.command[1]>>2)&1)) {
        BX_ERROR(("head number in command[1] doesn't match head field"));
        BX_FD_THIS s.status_reg0 = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive; // abnormal termination
        BX_FD_THIS s.status_reg1 = 0x04; // 0000 0100
        BX_FD_THIS s.status_reg2 = 0x00; // 0000 0000
        enter_result_phase();
        return;
      }

      if ( BX_FD_THIS s.media_present[drive] == 0 ) {
        // media not in drive, return error

        BX_INFO(("attempt to read/write sector %u,"
                     " sectors/track=%u with media not present", 
                     (unsigned) sector,
                     (unsigned) BX_FD_THIS s.media[drive].sectors_per_track));
        BX_FD_THIS s.status_reg0 = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive; // abnormal termination
        BX_FD_THIS s.status_reg1 = 0x25; // 0010 0101
        BX_FD_THIS s.status_reg2 = 0x31; // 0011 0001
        enter_result_phase();
        return;
        }

      if (sector_size != 0x02) { // 512 bytes
        BX_PANIC(("read/write command: sector size %d not supported", 128<<sector_size));
        }
      if ( cylinder >= BX_FD_THIS s.media[drive].tracks ) {
        BX_PANIC(("io: norm r/w parms out of range: sec#%02xh cyl#%02xh eot#%02xh head#%02xh",
          (unsigned) sector, (unsigned) cylinder, (unsigned) eot,
          (unsigned) head));
        return;
      }

      if (sector > BX_FD_THIS s.media[drive].sectors_per_track) {
        // requested sector > last sector on track
        BX_INFO(("attempt to read/write sector %u,"
                     " sectors/track=%u", (unsigned) sector,
                     (unsigned) BX_FD_THIS s.media[drive].sectors_per_track));
        // set controller to where drive would have left off
        // after it discovered the sector was past EOT
        BX_FD_THIS s.cylinder[drive] = cylinder;
        BX_FD_THIS s.head[drive]     = head;
        BX_FD_THIS s.sector[drive]   = BX_FD_THIS s.media[drive].sectors_per_track;

        // 0100 0HDD abnormal termination
        BX_FD_THIS s.status_reg0 = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive;
        // 1000 0101 end of cyl/NDAT/NID
        BX_FD_THIS s.status_reg1 = 0x85;
        // 0000 0000
        BX_FD_THIS s.status_reg2 = 0x00;
        enter_result_phase();
        return;
        }

      if (cylinder != BX_FD_THIS s.cylinder[drive])
        BX_DEBUG(("io: cylinder request != current cylinder"));

        // original assumed all floppies had two sides...now it does not  *delete this comment line*
        logical_sector = (cylinder * BX_FD_THIS s.media[drive].heads * BX_FD_THIS s.media[drive].sectors_per_track) +
                       (head * BX_FD_THIS s.media[drive].sectors_per_track) +
                       (sector - 1);

      if (logical_sector >= BX_FD_THIS s.media[drive].sectors) {
        BX_PANIC(("io: logical sector out of bounds"));
        }

      BX_FD_THIS s.cylinder[drive] = cylinder;
      BX_FD_THIS s.sector[drive]   = sector;
      BX_FD_THIS s.head[drive]     = head;

      if ((BX_FD_THIS s.command[0] & 0x4f) == 0x46) { // read
        floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
                    512, FROM_FLOPPY);

        DEV_dma_set_drq(FLOPPY_DMA_CHAN, 1);

        /* data reg not ready, controller busy */
        BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
        return;
        }
      else if ((BX_FD_THIS s.command[0] & 0x7f) == 0x45) { // write

        DEV_dma_set_drq(FLOPPY_DMA_CHAN, 1);

        /* data reg not ready, controller busy */
        BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
        return;
        }
      else
        BX_PANIC(("floppy_command(): unknown read/write command"));

      return;
      break;

    default: // invalid or unsupported command; these are captured in write() above
      BX_PANIC(("You should never get here! cmd = 0x%02x", 
                BX_FD_THIS s.command[0]));
    }
#endif
}

  void
bx_floppy_ctrl_c::floppy_xfer(Bit8u drive, Bit32u offset, Bit8u *buffer,
            Bit32u bytes, Bit8u direction)
{
  int ret;

  if (BX_FD_THIS s.device_type[drive] == BX_FLOPPY_NONE)
    BX_PANIC(("floppy_xfer: bad drive #%d", drive));

  if (bx_dbg.floppy) {
    BX_INFO(("drive=%u", (unsigned) drive));
    BX_INFO(("offset=%u", (unsigned) offset));
    BX_INFO(("bytes=%u", (unsigned) bytes));
    BX_INFO(("direction=%s", (direction==FROM_FLOPPY)? "from" : "to"));
    }

#if BX_WITH_MACOS
  if (strcmp(bx_options.floppya.Opath->getptr (), SuperDrive))
#endif
    {
    ret = lseek(BX_FD_THIS s.media[drive].fd, offset, SEEK_SET);
    if (ret < 0) {
      BX_PANIC(("could not perform lseek() on floppy image file"));
      }
    }

  if (direction == FROM_FLOPPY) {
#if BX_WITH_MACOS
    if (!strcmp(bx_options.floppya.Opath->getptr (), SuperDrive))
      ret = fd_read((char *) buffer, offset, bytes);
    else
#endif
      ret = ::read(BX_FD_THIS s.media[drive].fd, (bx_ptr_t) buffer, bytes);
    if (ret < int(bytes)) {
      /* ??? */
      if (ret > 0) {
        BX_INFO(("partial read() on floppy image returns %u/%u",
          (unsigned) ret, (unsigned) bytes));
        memset(buffer + ret, 0, bytes - ret);
        }
      else {
        BX_INFO(("read() on floppy image returns 0"));
        memset(buffer, 0, bytes);
        }
      }
    }

  else { // TO_FLOPPY
    BX_ASSERT (!BX_FD_THIS s.media[drive].write_protected);
#if BX_WITH_MACOS
    if (!strcmp(bx_options.floppya.Opath->getptr (), SuperDrive))
      ret = fd_write((char *) buffer, offset, bytes);
    else
#endif
      ret = ::write(BX_FD_THIS s.media[drive].fd, (bx_ptr_t) buffer, bytes);
    if (ret < int(bytes)) {
      BX_PANIC(("could not perform write() on floppy image file"));
    }
  }
}



  void
bx_floppy_ctrl_c::timer_handler(void *this_ptr)
{

  bx_floppy_ctrl_c *class_ptr = (bx_floppy_ctrl_c *) this_ptr;

  class_ptr->timer();
}

  void
bx_floppy_ctrl_c::timer()
{
  Bit8u drive;

  drive = BX_FD_THIS s.DOR & 0x03;
  switch ( BX_FD_THIS s.pending_command ) {
    case 0x07: // recal
    case 0x0f: // seek
      BX_FD_THIS s.status_reg0 = 0x20 | (BX_FD_THIS s.head[drive]<<2) | drive;
      if (BX_FD_THIS s.device_type[drive] == BX_FLOPPY_NONE) {
        BX_FD_THIS s.status_reg0 |= 0x50;
        }
      else if (BX_FD_THIS s.media_present[drive] == 0) {
        BX_FD_THIS s.status_reg0 |= 0x40;
        BX_FD_THIS s.status_reg1 = 0x25;
        BX_FD_THIS s.status_reg2 = 0x31;
        }

      /* reset changeline */
      if (drive > 1) return;
      if (BX_FD_THIS s.media_present[drive])
        BX_FD_THIS s.DIR[drive] &= ~0x80; // clear disk change line

      enter_idle_phase();
      raise_interrupt();
      break;

    case 0x4a: /* read ID */
      enter_result_phase();
      break;

    case 0xfe: // (contrived) RESET
      theFloppyController->reset(BX_RESET_SOFTWARE);
      BX_FD_THIS s.pending_command = 0;
      BX_FD_THIS s.status_reg0 = 0xc0;
      raise_interrupt();
      BX_FD_THIS s.reset_sensei = 4;
      break;
    
    case 0x00: // nothing pending?
      break;

    default:
      BX_PANIC(("floppy:timer(): unknown case %02x",
        (unsigned) BX_FD_THIS s.pending_command));
    }
  return;
}

  void
bx_floppy_ctrl_c::dma_write(Bit8u *data_byte)
{
  // A DMA write is from I/O to Memory
  // We need to return then next data byte from the floppy buffer
  // to be transfered via the DMA to memory. (read block from floppy)


  *data_byte = BX_FD_THIS s.floppy_buffer[BX_FD_THIS s.floppy_buffer_index++];

  if (BX_FD_THIS s.floppy_buffer_index >= 512) {
    Bit8u drive;

    drive = BX_FD_THIS s.DOR & 0x03;
    increment_sector(); // increment to next sector before retrieving next one
    BX_FD_THIS s.floppy_buffer_index = 0;
    if (DEV_dma_get_tc()) { // Terminal Count line, done
      BX_FD_THIS s.status_reg0 = (BX_FD_THIS s.head[drive] << 2) | drive;
      BX_FD_THIS s.status_reg1 = 0;
      BX_FD_THIS s.status_reg2 = 0;

      if (bx_dbg.floppy) {
        BX_INFO(("<<READ DONE>>"));
        BX_INFO(("AFTER"));
        BX_INFO(("  drive    = %u", (unsigned) drive));
        BX_INFO(("  head     = %u", (unsigned) BX_FD_THIS s.head[drive]));
        BX_INFO(("  cylinder = %u", (unsigned) BX_FD_THIS s.cylinder[drive]));
        BX_INFO(("  sector   = %u", (unsigned) BX_FD_THIS s.sector[drive]));
        }

      DEV_dma_set_drq(FLOPPY_DMA_CHAN, 0);
      enter_result_phase();
      }
    else { // more data to transfer
      Bit32u logical_sector;

      // original assumed all floppies had two sides...now it does not  *delete this comment line*
      logical_sector = (BX_FD_THIS s.cylinder[drive] * BX_FD_THIS s.media[drive].heads *
                        BX_FD_THIS s.media[drive].sectors_per_track) +
                       (BX_FD_THIS s.head[drive] *
                        BX_FD_THIS s.media[drive].sectors_per_track) +
                       (BX_FD_THIS s.sector[drive] - 1);

      floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
                  512, FROM_FLOPPY);
      }
    }
}

  void
bx_floppy_ctrl_c::dma_read(Bit8u *data_byte)
{
  // A DMA read is from Memory to I/O
  // We need to write the data_byte which was already transfered from memory
  // via DMA to I/O (write block to floppy)

  Bit8u drive;
  Bit32u logical_sector;

  drive = BX_FD_THIS s.DOR & 0x03;
  if (BX_FD_THIS s.pending_command == 0x4d) { // format track in progress
    --BX_FD_THIS s.format_count;
    switch (3 - (BX_FD_THIS s.format_count & 0x03)) {
      case 0:
        BX_FD_THIS s.cylinder[drive] = *data_byte;
        break;
      case 1:
        if (*data_byte != BX_FD_THIS s.head[drive])
          BX_ERROR(("head number does not match head field"));
        break;
      case 2:
        BX_FD_THIS s.sector[drive] = *data_byte;
        break;
      case 3:
        if (*data_byte != 2) BX_ERROR(("dma_read: sector size %d not supported", 128<<(*data_byte)));
        BX_DEBUG(("formatting cylinder %u head %u sector %u",
                  BX_FD_THIS s.cylinder[drive], BX_FD_THIS s.head[drive],
                  BX_FD_THIS s.sector[drive]));
        for (unsigned i = 0; i < 512; i++) {
          BX_FD_THIS s.floppy_buffer[i] = BX_FD_THIS s.format_fillbyte;
          }
        // original assumed all floppies had two sides...now it does not *delete this comment line*
        logical_sector = (BX_FD_THIS s.cylinder[drive] * BX_FD_THIS s.media[drive].heads * BX_FD_THIS s.media[drive].sectors_per_track) +
                         (BX_FD_THIS s.head[drive] * BX_FD_THIS s.media[drive].sectors_per_track) +
                         (BX_FD_THIS s.sector[drive] - 1);
        floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
                    512, TO_FLOPPY);
        break;
      }
    if ((BX_FD_THIS s.format_count == 0) || (DEV_dma_get_tc())) {
      BX_FD_THIS s.format_count = 0;
      BX_FD_THIS s.status_reg0 = (BX_FD_THIS s.head[drive] << 2) | drive;
      DEV_dma_set_drq(FLOPPY_DMA_CHAN, 0);
      enter_result_phase();
      }
    return;
    }

  BX_FD_THIS s.floppy_buffer[BX_FD_THIS s.floppy_buffer_index++] = *data_byte;

  if (BX_FD_THIS s.floppy_buffer_index >= 512) {
    // original assumed all floppies had two sides...now it does not *delete this comment line*
    logical_sector = (BX_FD_THIS s.cylinder[drive] * BX_FD_THIS s.media[drive].heads * BX_FD_THIS s.media[drive].sectors_per_track) +
                     (BX_FD_THIS s.head[drive] * BX_FD_THIS s.media[drive].sectors_per_track) +
                     (BX_FD_THIS s.sector[drive] - 1);
  if ( BX_FD_THIS s.media[drive].write_protected ) {
    // write protected error
    BX_INFO(("tried to write disk %u, which is write-protected", drive));
    // ST0: IC1,0=01  (abnormal termination: started execution but failed)
    BX_FD_THIS s.status_reg0 = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive;
    // ST1: DataError=1, NDAT=1, NotWritable=1, NID=1
    BX_FD_THIS s.status_reg1 = 0x27; // 0010 0111
    // ST2: CRCE=1, SERR=1, BCYL=1, NDAM=1.
    BX_FD_THIS s.status_reg2 = 0x31; // 0011 0001
    enter_result_phase();
    return;
    }
    floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
                512, TO_FLOPPY);
    increment_sector(); // increment to next sector after writing current one
    BX_FD_THIS s.floppy_buffer_index = 0;
    if (DEV_dma_get_tc()) { // Terminal Count line, done
      BX_FD_THIS s.status_reg0 = (BX_FD_THIS s.head[drive] << 2) | drive;
      BX_FD_THIS s.status_reg1 = 0;
      BX_FD_THIS s.status_reg2 = 0;

      if (bx_dbg.floppy) {
        BX_INFO(("<<WRITE DONE>>"));
        BX_INFO(("AFTER"));
        BX_INFO(("  drive    = %u", (unsigned) drive));
        BX_INFO(("  head     = %u", (unsigned) BX_FD_THIS s.head[drive]));
        BX_INFO(("  cylinder = %u", (unsigned) BX_FD_THIS s.cylinder[drive]));
        BX_INFO(("  sector   = %u", (unsigned) BX_FD_THIS s.sector[drive]));
        }

      DEV_dma_set_drq(FLOPPY_DMA_CHAN, 0);
      enter_result_phase();
      }
    else { // more data to transfer
      } // else
    } // if BX_FD_THIS s.floppy_buffer_index >= 512
}

  void
bx_floppy_ctrl_c::raise_interrupt(void)
{
  DEV_pic_raise_irq(6);
  BX_FD_THIS s.pending_irq = 1;
  BX_FD_THIS s.reset_sensei = 0;
}


  void
bx_floppy_ctrl_c::increment_sector(void)
{
  Bit8u drive;

  drive = BX_FD_THIS s.DOR & 0x03;

  // values after completion of data xfer
  // ??? calculation depends on base_count being multiple of 512
  BX_FD_THIS s.sector[drive] ++;
  if (BX_FD_THIS s.sector[drive] > BX_FD_THIS s.media[drive].sectors_per_track) {
    BX_FD_THIS s.sector[drive] = 1;
    if (BX_FD_THIS s.multi_track) {
      BX_FD_THIS s.head[drive] ++;
      if (BX_FD_THIS s.head[drive] > 1) {
        BX_FD_THIS s.head[drive] = 0;
        BX_FD_THIS s.cylinder[drive] ++;
        }
      }
    else {
      BX_FD_THIS s.cylinder[drive] ++;
      }
    if (BX_FD_THIS s.cylinder[drive] >= BX_FD_THIS s.media[drive].tracks) {
      // Set to 1 past last possible cylinder value.
      // I notice if I set it to tracks-1, prama linux won't boot.
      BX_FD_THIS s.cylinder[drive] = BX_FD_THIS s.media[drive].tracks;
      BX_INFO(("increment_sector: clamping cylinder to max"));
      }
    }
}

  unsigned
bx_floppy_ctrl_c::set_media_status(unsigned drive, unsigned status)
{
  char *path;
  unsigned type;

  if (drive == 0)
    type = bx_options.floppya.Otype->get ();
  else
    type = bx_options.floppyb.Otype->get ();

  // if setting to the current value, nothing to do
  if ((status == BX_FD_THIS s.media_present[drive]) &&
      ((status == 0) || (type == BX_FD_THIS s.media[drive].type)))
    return(status);

  if (status == 0) {
    // eject floppy
    if (BX_FD_THIS s.media[drive].fd >= 0) {
      close( BX_FD_THIS s.media[drive].fd );
      BX_FD_THIS s.media[drive].fd = -1;
      }
    BX_FD_THIS s.media_present[drive] = 0;
    if (drive == 0) {
      bx_options.floppya.Ostatus->set(BX_EJECTED);
    } else {
      bx_options.floppyb.Ostatus->set(BX_EJECTED);
    }
    BX_FD_THIS s.DIR[drive] |= 0x80; // disk changed line
    return(0);
    }
  else {
    // insert floppy
    if (drive == 0) {
      path = bx_options.floppya.Opath->getptr ();
      }
    else {
      path = bx_options.floppyb.Opath->getptr ();
      }
    if (!strcmp(path, "none"))
      return(0);
    if (evaluate_media(type, path, & BX_FD_THIS s.media[drive])) {
      BX_FD_THIS s.media_present[drive] = 1;
      if (drive == 0) {
#define MED (BX_FD_THIS s.media[0])
        BX_INFO(("fd0: '%s' ro=%d, h=%d,t=%d,spt=%d", bx_options.floppya.Opath->getptr(),
        MED.write_protected, MED.heads, MED.tracks, MED.sectors_per_track));
#undef MED
        bx_options.floppya.Ostatus->set(BX_INSERTED);
      } else {
#define MED (BX_FD_THIS s.media[1])
        BX_INFO(("fd1: '%s' ro=%d, h=%d,t=%d,spt=%d", bx_options.floppyb.Opath->getptr(),
        MED.write_protected, MED.heads, MED.tracks, MED.sectors_per_track));
#undef MED
        bx_options.floppyb.Ostatus->set(BX_INSERTED);
      }
      BX_FD_THIS s.DIR[drive] |= 0x80; // disk changed line
      return(1);
      }
    else {
      BX_FD_THIS s.media_present[drive] = 0;
      if (drive == 0) {
        bx_options.floppya.Ostatus->set(BX_EJECTED);
      } else {
        bx_options.floppyb.Ostatus->set(BX_EJECTED);
      }
      return(0);
      }
    }
}

  unsigned
bx_floppy_ctrl_c::get_media_status(unsigned drive)
{
  return( BX_FD_THIS s.media_present[drive] );
}

#ifdef O_BINARY
#define BX_RDONLY O_RDONLY | O_BINARY
#define BX_RDWR O_RDWR | O_BINARY
#else
#define BX_RDONLY O_RDONLY
#define BX_RDWR O_RDWR
#endif

  bx_bool
bx_floppy_ctrl_c::evaluate_media(unsigned type, char *path, floppy_t *media)
{
  struct stat stat_buf;
  int i, ret;
  int idx = -1;
#ifdef __linux__
  struct floppy_struct floppy_geom;
#endif
#ifdef WIN32
  char sTemp[1024];
  bx_bool raw_floppy = 0;
  HANDLE hFile;
  DWORD bytes;
  DISK_GEOMETRY dg;
  unsigned tracks, heads, spt;
#endif

  if (type == BX_FLOPPY_NONE)
    return(0);

  //If media file is already open, close it before reopening.
  if(media->fd >=0) {
    close(media->fd);
    media->fd=-1;
  }

  // open media file (image file or device)
  media->write_protected = 0;
#ifdef macintosh
  media->fd = 0;
  if (strcmp(bx_options.floppya.Opath->getptr (), SuperDrive))
#endif
#ifdef WIN32
    if ( (isalpha(path[0])) && (path[1] == ':') && (strlen(path) == 2) ) {
      raw_floppy = 1;
      wsprintf(sTemp, "\\\\.\\%s", path);
      hFile = CreateFile(sTemp, GENERIC_READ, FILE_SHARE_WRITE, NULL,
                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      if (hFile == INVALID_HANDLE_VALUE) {
        BX_ERROR(("Cannot open floppy drive"));
        return(0);
      } else {
        if (!DeviceIoControl(hFile, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &dg, sizeof(dg), &bytes, NULL)) {
          BX_ERROR(("No media in floppy drive"));
          CloseHandle(hFile);
          return(0);
        } else {
          tracks = (unsigned)dg.Cylinders.QuadPart;
          heads  = (unsigned)dg.TracksPerCylinder;
          spt    = (unsigned)dg.SectorsPerTrack;
        }
        CloseHandle(hFile);
      }
      media->fd = open(sTemp, BX_RDWR);
    } else {
      media->fd = open(path, BX_RDWR);
    } 
#else
    media->fd = open(path, BX_RDWR);
#endif

  if (media->fd < 0) {
    BX_INFO(( "tried to open '%s' read/write: %s",path,strerror(errno) ));
    // try opening the file read-only
    media->write_protected = 1;
#ifdef macintosh
  media->fd = 0;
  if (strcmp(bx_options.floppya.Opath->getptr (), SuperDrive))
#endif
#ifdef WIN32
    if (raw_floppy == 1) {
      media->fd = open(sTemp, BX_RDONLY);
    } else {
      media->fd = open(path, BX_RDONLY);
    }
#else
    media->fd = open(path, BX_RDONLY);
#endif
    if (media->fd < 0) {
      // failed to open read-only too
      BX_INFO(( "tried to open '%s' read only: %s",path,strerror(errno) ));
      media->type = type;
      return(0);
    }
  }

#if BX_WITH_MACOS
  if (!strcmp(bx_options.floppya.Opath->getptr (), SuperDrive))
    ret = fd_stat(&stat_buf);
  else
    ret = fstat(media->fd, &stat_buf);
#elif defined(WIN32)
  if (raw_floppy) {
    memset (&stat_buf, 0, sizeof(stat_buf));
    stat_buf.st_mode = S_IFCHR;
    ret = 0;
  } else {
    ret = fstat(media->fd, &stat_buf);
  }
#else
  // unix
  ret = fstat(media->fd, &stat_buf);
#endif
  if (ret) {
    BX_PANIC(("fstat floppy 0 drive image file returns error: %s", strerror(errno)));
    return(0);
    }

  for (i = 0; i < 8; i++) {
    if (type == floppy_type[i].id) idx = i;
  }
  if (idx == -1 ) {
    BX_PANIC(("evaluate_media: unknown media type"));
    return(0);
  }
  if ( S_ISREG(stat_buf.st_mode) ) {
    // regular file
    switch (type) {
      // use CMOS reserved types
      case BX_FLOPPY_160K: // 160K 5.25"
      case BX_FLOPPY_180K: // 180K 5.25"
      case BX_FLOPPY_320K: // 320K 5.25"
      // standard floppy types
      case BX_FLOPPY_360K: // 360K 5.25"
      case BX_FLOPPY_720K: // 720K 3.5"
      case BX_FLOPPY_1_2: // 1.2M 5.25"
      case BX_FLOPPY_2_88: // 2.88M 3.5"
        media->type              = type;
        media->tracks            = floppy_type[idx].trk;
        media->heads             = floppy_type[idx].hd;
        media->sectors_per_track = floppy_type[idx].spt;
        media->sectors           = floppy_type[idx].sectors;
        if (stat_buf.st_size > (media->sectors * 512)) {
          BX_INFO(("evaluate_media: size of file '%s' (%lu) too large for selected type",
                   path, (unsigned long) stat_buf.st_size));
          return(0);
        }
        break;
      default: // 1.44M 3.5"
        media->type              = type;
        if (stat_buf.st_size <= 1474560) {
          media->tracks            = floppy_type[idx].trk;
          media->heads             = floppy_type[idx].hd;
          media->sectors_per_track = floppy_type[idx].spt;
          }
        else if (stat_buf.st_size == 1720320) {
          media->sectors_per_track = 21;
          media->tracks            = 80;
          media->heads             = 2;
          }
        else if (stat_buf.st_size == 1763328) {
          media->sectors_per_track = 21;
          media->tracks            = 82;
          media->heads             = 2;
          }
        else {
          BX_INFO(("evaluate_media: file '%s' of unknown size %lu",
            path, (unsigned long) stat_buf.st_size));
          return(0);
          }
        media->sectors = media->heads * media->tracks * media->sectors_per_track;
      }
    return(1); // success
    }

  else if ( S_ISCHR(stat_buf.st_mode)
#if BX_WITH_MACOS == 0
#ifdef S_ISBLK
            || S_ISBLK(stat_buf.st_mode)
#endif
#endif
           ) {
    // character or block device
    // assume media is formatted to typical geometry for drive
    media->type              = type;
#ifdef __linux__
    if (ioctl(media->fd, FDGETPRM, &floppy_geom) < 0) {
      BX_ERROR(("cannot determine media geometry"));
      return(0);
    }
    media->tracks            = floppy_geom.track;
    media->heads             = floppy_geom.head;
    media->sectors_per_track = floppy_geom.sect;
    media->sectors           = floppy_geom.size;
#elif defined(WIN32)
    media->tracks            = tracks;
    media->heads             = heads;
    media->sectors_per_track = spt;
    media->sectors = media->heads * media->tracks * media->sectors_per_track;
#else
    media->tracks            = floppy_type[idx].trk;
    media->heads             = floppy_type[idx].hd;
    media->sectors_per_track = floppy_type[idx].spt;
    media->sectors           = floppy_type[idx].sectors;
#endif
    return(1); // success
    }
  else {
    // unknown file type
    BX_INFO(("unknown mode type"));
    return(0);
    }
}


void
bx_floppy_ctrl_c::enter_result_phase(void)
{

  Bit8u drive;

  drive = BX_FD_THIS s.DOR & 0x03;

  /* these are always the same */
  BX_FD_THIS s.result_index = 0;
  BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;

  /* invalid command */
  if ((BX_FD_THIS s.status_reg0 & 0xc0) == 0x80) {
    BX_FD_THIS s.result_size = 1;
    BX_FD_THIS s.result[0] = BX_FD_THIS s.status_reg0;
    return;
  } 

  switch (BX_FD_THIS s.pending_command) {
  case 0x04: // get status
    BX_FD_THIS s.result_size = 1;
    BX_FD_THIS s.result[0] = BX_FD_THIS s.status_reg3;
    break;
  case 0x08: // sense interrupt
    BX_FD_THIS s.result_size = 2;
    BX_FD_THIS s.result[0] = BX_FD_THIS s.status_reg0;
    BX_FD_THIS s.result[1] = BX_FD_THIS s.cylinder[drive];
    break;
  case 0x4a: // read ID
  case 0x4d: // format track
  case 0x46: // read normal data
  case 0x66:
  case 0xc6:
  case 0xe6:
  case 0x45: // write normal data
  case 0xc5:
    BX_FD_THIS s.result_size = 7;
    BX_FD_THIS s.result[0] = BX_FD_THIS s.status_reg0;    
    BX_FD_THIS s.result[1] = BX_FD_THIS s.status_reg1;
    BX_FD_THIS s.result[2] = BX_FD_THIS s.status_reg2;
    BX_FD_THIS s.result[3] = BX_FD_THIS s.cylinder[drive];
    BX_FD_THIS s.result[4] = BX_FD_THIS s.head[drive];
    BX_FD_THIS s.result[5] = BX_FD_THIS s.sector[drive];
    BX_FD_THIS s.result[6] = 2; /* sector size code */
    raise_interrupt();
    break;
  }
}

void
bx_floppy_ctrl_c::enter_idle_phase(void)
{
  BX_FD_THIS s.main_status_reg &= 0x0f;      // leave drive status untouched
  BX_FD_THIS s.main_status_reg |= FD_MS_MRQ; // data register ready

  BX_FD_THIS s.command_complete = 1; /* waiting for new command */
  BX_FD_THIS s.command_index = 0;
  BX_FD_THIS s.command_size = 0;
  BX_FD_THIS s.pending_command = 0;

  BX_FD_THIS s.floppy_buffer_index = 0;
}